การส่งต่อการประกาศประเภท / คลาสที่ซ้อนกันใน C ++


197

ฉันเพิ่งติดอยู่ในสถานการณ์เช่นนี้:

class A
{
public:
    typedef struct/class {...} B;
...
    C::D *someField;
}

class C
{
public:
    typedef struct/class {...} D;
...
    A::B *someField;
}

โดยปกติคุณสามารถประกาศชื่อคลาสได้:

class A;

แต่คุณไม่สามารถส่งต่อชนิดที่ซ้อนกันได้สาเหตุต่อไปนี้เป็นข้อผิดพลาดในการรวบรวม

class C::D;

ความคิดใด ๆ


6
ทำไมคุณต้องการสิ่งนั้น โปรดทราบว่าคุณสามารถส่งต่อประกาศหากเป็นสมาชิกของคลาสเดียวกันที่ถูกกำหนด: class X {class Y; Y * a; }; คลาส X :: Y {};
Johannes Schaub - litb

โซลูชันนี้ใช้งานได้สำหรับฉัน (เนมสเปซ C {class D;};): stackoverflow.com/questions/22389784/…
อัลเบิร์ตวีร์สช

ฉันพบลิงก์
bitlixi

คำตอบ:


224

คุณทำไม่ได้มันเป็นช่องโหว่ในภาษา C ++ คุณจะต้องยกเลิกการซ่อนอย่างน้อยหนึ่งคลาสที่ซ้อนกัน


6
ขอบคุณสำหรับคำตอบ. ในกรณีของฉันพวกเขาไม่ใช่ชนชั้นที่ซ้อนกันของฉัน ฉันหวังว่าจะหลีกเลี่ยงการพึ่งพาไฟล์ส่วนหัวของไลบรารีขนาดใหญ่พร้อมการอ้างอิงไปข้างหน้าเล็กน้อย ฉันสงสัยว่า C ++ 11 แก้ไขหรือไม่
Marsh Ray

61
โอ้ สิ่งที่ฉันไม่ต้องการให้ google แสดง ขอบคุณต่อไปสำหรับคำตอบที่กระชับ
เรียนรู้

19
เหมือนกันที่นี่ ... มีใครรู้บ้างไหมว่าทำไมจึงเป็นไปไม่ได้ ดูเหมือนว่ามีกรณีการใช้ที่ถูกต้องและการขาดนี้จะป้องกันความสอดคล้องของสถาปัตยกรรมในบางสถานการณ์
Maël Nison

คุณสามารถใช้เพื่อน และใส่ความคิดเห็นที่คุณใช้เพื่อทำงานรอบ ๆ รูใน C ++
Erik Aronesty

3
เมื่อใดก็ตามที่ฉันพบข้อบกพร่องที่ไม่มีความจำเป็นเช่นนี้ในภาษา ersatz ฉันจะฉีกขาดระหว่างการหัวเราะและร้องไห้
SongWithoutWords

33
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

ฉันต้องการการอ้างอิงล่วงหน้าเช่น:

class IDontControl::Nested; // But this doesn't work.

วิธีแก้ปัญหาของฉันคือ:

class IDontControl_Nested; // Forward reference to distinct name.

ต่อมาเมื่อฉันสามารถใช้คำจำกัดความเต็ม:

#include <idontcontrol.h>

// I defined the forward ref like this:
class IDontControl_Nested : public IDontControl::Nested
{
    // Needed to make a forwarding constructor here
    IDontControl_Nested(int i) : Nested(i) { }
};

เทคนิคนี้อาจเป็นปัญหามากกว่าที่ควรจะเป็นหากมีคอนสตรัคเตอร์ที่ซับซ้อนหรือฟังก์ชั่นสมาชิกพิเศษอื่น ๆ ที่ไม่ได้รับการถ่ายทอดอย่างราบรื่น ฉันจินตนาการได้เลยว่าเทมเพลตบางตัวมีปฏิกิริยาไม่ดี

แต่ในกรณีที่เรียบง่ายของฉันดูเหมือนว่าจะทำงาน


16
ใน C ++ 11 คุณสามารถสืบทอด constructors โดยusing basename::basename;ในคลาสที่ได้รับดังนั้นจึงไม่มีปัญหากับ cker ที่ซับซ้อน
Xeo

1
เคล็ดลับดี แต่มันจะไม่ทำงานถ้าตัวชี้ไปยัง IDontControl :: ซ้อนกันใช้ในส่วนหัวเดียวกัน (ที่มันไปข้างหน้าประกาศ) และเข้าถึงได้จากรหัสภายนอกซึ่งยังมีความหมายแบบเต็มของ IDontControl (เนื่องจากคอมไพเลอร์จะไม่ตรงกับ IDontControl_Nested และ IDontControl :: Nested) วิธีแก้ปัญหาคือทำการส่งสัญญาณแบบสแตติก
Artem Pisarenko

ฉันขอแนะนำให้ทำสิ่งที่ตรงกันข้ามและมีชั้นเรียนด้านนอกและใช้typedefในชั้นเรียน
Ridderhoff

3

หากคุณต้องการหลีกเลี่ยง #including ไฟล์ส่วนหัวที่น่ารังเกียจในไฟล์ส่วนหัวของคุณคุณสามารถทำได้:

ไฟล์ hpp:

class MyClass
{
public:
    template<typename ThrowAway>
    void doesStuff();
};

ไฟล์ cpp

#include "MyClass.hpp"
#include "Annoying-3rd-party.hpp"

template<> void MyClass::doesStuff<This::Is::An::Embedded::Type>()
{
    // ...
}

แต่แล้ว:

  1. คุณจะต้องระบุประเภทที่ฝังตัวในเวลาโทร (โดยเฉพาะอย่างยิ่งถ้าฟังก์ชั่นของคุณไม่ใช้พารามิเตอร์ใด ๆ ของประเภทฝังตัว)
  2. ฟังก์ชั่นของคุณไม่สามารถเป็นเสมือน (เพราะเป็นเทมเพลต)

ดังนั้นใช่การแลกเปลี่ยน ...


1
Heck คือhppไฟล์อะไร?
Naftali aka Neal

7
lol ไฟล์ส่วนหัว. hppถูกใช้ในโครงการ C ++ เพื่อแยกความแตกต่างจากไฟล์ส่วนหัว C ซึ่งโดยทั่วไปแล้วลงท้ายด้วย. h เมื่อทำงานกับ C ++ และ C ในโครงการเดียวกันบางคนชอบ. hpp และ. cpp สำหรับไฟล์ C ++ เพื่อให้ชัดเจนกับประเภทของไฟล์ที่พวกเขาจัดการกับและ. h และ. c สำหรับไฟล์ C
bitek

2

ซึ่งสามารถทำได้โดยการประกาศไปข้างหน้าชั้นนอกเป็นใน namespace

ตัวอย่าง: เราต้องใช้คลาสที่ซ้อนกันอื่น ๆ :: A :: ซ้อนใน others_a.h ซึ่งอยู่นอกเหนือการควบคุมของเรา

others_a.h

namespace others {
struct A {
    struct Nested {
        Nested(int i) :i(i) {}
        int i{};
        void print() const { std::cout << i << std::endl; }
    };
};
}

my_class.h

#ifndef MY_CLASS_CPP
// A is actually a class
namespace others { namespace A { class Nested; } }
#endif

class MyClass {
public:
    MyClass(int i);
    ~MyClass();
    void print() const;
private:
    std::unique_ptr<others::A::Nested> _aNested;
};

my_class.cpp

#include "others_a.h"
#define MY_CLASS_CPP // Must before include my_class.h
#include "my_class.h"

MyClass::MyClass(int i) :
    _aNested(std::make_unique<others::A::Nested>(i)) {}
MyClass::~MyClass() {}
void MyClass::print() const {
    _aNested->print();
}

1
อาจใช้งานได้ แต่ไม่มีเอกสาร สาเหตุที่ทำงานเป็นa::bmangled ในลักษณะเดียวกันไม่ว่าaจะเป็น class หรือ namespace
jaskmar

3
ไม่ทำงานกับ Clang หรือ GCC มันบอกว่าชั้นนอกถูกประกาศเป็นสิ่งที่แตกต่างจาก namespace
Dugi

1

ฉันจะไม่เรียกคำตอบนี้ แต่ก็พบว่าน่าสนใจ: ถ้าคุณทำซ้ำคำประกาศของโครงสร้างของคุณในเนมสเปซที่เรียกว่า C ทุกอย่างดี (ใน gcc อย่างน้อย) เมื่อพบคำจำกัดความของคลาส C ดูเหมือนว่าจะเขียนทับ namspace C.

namespace C {
    typedef struct {} D;
}

class A
{
public:
 typedef struct/class {...} B;
...
C::D *someField;
}

class C
{
public:
   typedef struct/class {...} D;
...
   A::B *someField;
}

1
ฉันลองมันด้วย cygwin gcc และมันไม่ได้คอมไพล์ถ้าคุณพยายามอ้างอิง A.someField C :: D ในคลาส A คำจำกัดความหมายถึงโครงสร้าง (ว่าง) ในเนมสเปซไม่ใช่โครงสร้างในคลาส C (BTW ซึ่งไม่ได้รวบรวมใน MSVC)
Dolphin

มันทำให้ข้อผิดพลาด: " 'คลาส C' redeclared เป็นชนิดที่แตกต่างกันของสัญลักษณ์"
Calmarius

9
ดูเหมือนว่าข้อบกพร่องของ GCC ดูเหมือนว่าชื่อเนมสเปซสามารถซ่อนชื่อคลาสในขอบเขตเดียวกันได้
Johannes Schaub - litb

0

นี่จะเป็นวิธีแก้ปัญหา (อย่างน้อยสำหรับปัญหาที่อธิบายในคำถาม - ไม่ใช่สำหรับปัญหาจริงเช่นเมื่อไม่สามารถควบคุมคำจำกัดความของC):

class C_base {
public:
    class D { }; // definition of C::D
    // can also just be forward declared, if it needs members of A or A::B
};
class A {
public:
    class B { };
    C_base::D *someField; // need to call it C_base::D here
};
class C : public C_base { // inherits C_base::D
public:
    // Danger: Do not redeclare class D here!!
    // Depending on your compiler flags, you may not even get a warning
    // class D { };
    A::B *someField;
};

int main() {
    A a;
    C::D * test = a.someField; // here it can be called C::D
}

0

หากคุณมีสิทธิ์เข้าถึงเพื่อเปลี่ยนซอร์สโค้ดของคลาส C และ D คุณสามารถนำคลาส D แยกต่างหากและป้อนคำพ้องความหมายในคลาส C:

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.