Przekazywanie deklaracji zagnieżdżonych typów / klas w C ++

197

Niedawno utknąłem w takiej sytuacji:

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

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

Zwykle możesz zadeklarować nazwę klasy:

class A;

Nie można jednak zadeklarować typu zagnieżdżonego, ponieważ powoduje to błąd kompilacji.

class C::D;

Jakieś pomysły?

Calmarius
źródło
6
Dlaczego tego potrzebujesz? Zauważ, że możesz przekazać dalej, jeśli jest zdefiniowany członek tej samej klasy: klasa X {klasa Y; Y * a; }; klasa X :: Y {};
Johannes Schaub - litb
To rozwiązanie zadziałało dla mnie (przestrzeń nazw C {klasa D;};): stackoverflow.com/questions/22389784/...
Albert Wiersch
Znalazłem link do
bitlixi

Odpowiedzi:

224

Nie możesz tego zrobić, to dziura w języku C ++. Musisz odblokować co najmniej jedną z zagnieżdżonych klas.

Adam Rosenfield
źródło
6
Dziękuję za odpowiedź. W moim przypadku nie są to moje zagnieżdżone klasy. Miałem nadzieję, że uniknę ogromnej zależności pliku nagłówka biblioteki od małego odniesienia do przodu. Zastanawiam się, czy C ++ 11 to naprawił?
Marsh Ray
61
O. Właśnie tego nie chciałem, aby Google się pojawił. W każdym razie dziękuję za zwięzłą odpowiedź.
learnvst
19
To samo tutaj ... czy ktoś wie, dlaczego nie jest to możliwe? Wydaje się, że istnieją prawidłowe przypadki użycia, a brak ten uniemożliwia spójność architektury w niektórych sytuacjach.
Maël Nison,
Możesz użyć znajomego. I po prostu dodaj komentarz, że używasz go do obejścia dziury w C ++.
Erik Aronesty
3
Ilekroć napotykam tak niepotrzebne wady w tym języku ersatz, jestem rozdarty między śmiechem a płaczem
SongWithoutWords
33
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

Potrzebowałem referencji takich jak:

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

Moim obejściem było:

class IDontControl_Nested; // Forward reference to distinct name.

Później, kiedy mogłem użyć pełnej definicji:

#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) { }
};

Ta technika byłaby prawdopodobnie większym problemem niż jest warta, gdyby istniały skomplikowane konstruktory lub inne specjalne funkcje składowe, które nie zostałyby odziedziczone płynnie. Mogłem sobie wyobrazić, że pewna magia szablonu źle reaguje.

Ale w moim bardzo prostym przypadku wydaje się, że działa.

Marsh Ray
źródło
16
W C ++ 11 możesz dziedziczyć konstruktory według using basename::basename;klasy pochodnej, więc nie ma problemu ze skomplikowanymi wskaźnikami.
Xeo
1
Fajna sztuczka, ale nie zadziała, jeśli wskaźnik do IDontControl :: Nested zostanie użyty w tym samym nagłówku (tam, gdzie został zadeklarowany w przód) i będzie dostępny z zewnętrznego kodu, który zawiera również pełną definicję IDontControl. (Ponieważ kompilator nie będzie pasował do IDontControl_Nested i IDontControl :: Nested). Obejściem tego problemu jest wykonanie rzutowania statycznego.
Artem Pisarenko,
typedef
Poleciłbym
3

Jeśli naprawdę chcesz uniknąć # włączenia nieprzyjemnego pliku nagłówka do pliku nagłówka, możesz to zrobić:

plik hpp:

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

plik CPP

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

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

Ale wtedy:

  1. będziesz musiał określić typ osadzony w czasie połączenia (szczególnie jeśli twoja funkcja nie przyjmuje żadnych parametrów typu osadzonego)
  2. twoja funkcja nie może być wirtualna (ponieważ jest to szablon)

Więc tak, kompromisy ...

Edenbridge
źródło
1
Czym do cholery jest hppplik?
Naftali alias Neal
7
lol, plik nagłówka .hpp jest używany w projektach C ++ do odróżnienia go od pliku nagłówka C, który zwykle kończy się na .h. Podczas pracy z C ++ i C w tym samym projekcie niektórzy wolą pliki .hpp i .cpp dla plików C ++, aby wyraźnie określić, z jakim typem plików mają do czynienia, oraz .h i .c dla plików C.
bitek,
2

Można to zrobić przez zadeklarowanie klasy zewnętrznej jako przestrzeni nazw .

Przykład: musimy użyć zagnieżdżonej klasy others :: A :: Nested in others_a.h, która jest poza naszą kontrolą.

inne_a.h

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

moja_klasa.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;
};

moja_klasa.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();
}
bitlixi
źródło
1
Może działać, ale jest nieudokumentowane. Powodem, dla którego działa, a::bjest zniekształcone w ten sam sposób, bez względu na ato, czy jest to klasa, czy przestrzeń nazw.
jaskmar
3
Nie działa z Clang ani GCC. Mówi, że klasa zewnętrzna została zadeklarowana jako coś innego niż przestrzeń nazw.
Dugi
1

Nie nazwałbym tego odpowiedzią, ale mimo to ciekawym znaleziskiem: jeśli powtórzysz deklarację swojej struktury w przestrzeni nazw o nazwie C, wszystko jest w porządku (przynajmniej w gcc). Po znalezieniu definicji klasy C wydaje się, że dyskretnie zastępuje obszar nazw 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;
}
nschmidt
źródło
1
Próbowałem tego z cygwin gcc i nie można go skompilować, jeśli spróbujesz odwołać się do A.someField. C :: D w definicji klasy A tak naprawdę odnosi się do (pustej) struktury w przestrzeni nazw, a nie do struktury w klasie C (BTW, to nie kompiluje się w MSVC)
Dolphin
Daje błąd: „„ klasa C ”została ponownie zadeklarowana jako inny rodzaj symbolu”
Calmarius
9
Wygląda jak błąd GCC. Wydaje się, że nazwa przestrzeni nazw może ukryć nazwę klasy w tym samym zakresie.
Johannes Schaub - litb
0

Byłoby to obejście (przynajmniej dla problemu opisanego w pytaniu - nie dla rzeczywistego problemu, tj. Gdy nie ma się kontroli nad definicją 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
}
chtz
źródło
0

Jeśli masz dostęp do zmiany kodu źródłowego klas C i D, możesz osobno wyjąć klasę D i wprowadzić jej synonim w klasie C:

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
Suworow Iwan
źródło