Weiterleitungsdeklaration verschachtelter Typen / Klassen in C ++

197

Ich bin kürzlich in einer Situation wie dieser festgefahren:

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

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

Normalerweise können Sie einen Klassennamen deklarieren:

class A;

Sie können einen verschachtelten Typ jedoch nicht weiterleiten. Folgendes führt zu einem Kompilierungsfehler.

class C::D;

Irgendwelche Ideen?

Calmarius
quelle
6
Warum brauchst du das? Beachten Sie, dass Sie die Deklaration weiterleiten können, wenn es sich um ein Mitglied derselben Klasse handelt, die definiert wird: Klasse X {Klasse Y; Y * a; }; Klasse X :: Y {};
Johannes Schaub - litb
Diese Lösung funktionierte für mich (Namespace C {Klasse D;};): stackoverflow.com/questions/22389784/…
Albert Wiersch
Ich fand eine Lösung Link
bitlixi

Antworten:

224

Sie können es nicht tun, es ist ein Loch in der C ++ - Sprache. Sie müssen mindestens eine der verschachtelten Klassen aufheben.

Adam Rosenfield
quelle
6
Danke für die Antwort. In meinem Fall sind sie nicht meine verschachtelten Klassen. Ich hatte gehofft, eine große Abhängigkeit von Bibliotheks-Header-Dateien mit einer kleinen Vorwärtsreferenz zu vermeiden. Ich frage mich, ob C ++ 11 das Problem behoben hat.
Marsh Ray
61
Oh. Genau das, was ich nicht wollte, dass Google auftaucht. Trotzdem danke für die prägnante Antwort.
Learnvst
19
Gleich hier ... weiß jemand, warum es nicht möglich ist? Es scheint, dass es gültige Anwendungsfälle gibt, und dieser Mangel verhindert in einigen Situationen die Konsistenz der Architektur.
Maël Nison
Sie können Freund verwenden. Und geben Sie einfach einen Kommentar ein, dass Sie damit das Loch in C ++ umgehen.
Erik Aronesty
3
Immer wenn ich auf solche unnötigen Fehler in dieser Ersatzsprache stoße, bin ich zwischen Lachen und Weinen hin und her gerissen
SongWithoutWords
33
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

Ich brauchte eine Vorwärtsreferenz wie:

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

Meine Problemumgehung war:

class IDontControl_Nested; // Forward reference to distinct name.

Später, als ich die vollständige Definition verwenden konnte:

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

Diese Technik wäre wahrscheinlich schwieriger als es sich lohnt, wenn es komplizierte Konstruktoren oder andere spezielle Elementfunktionen gäbe, die nicht reibungslos vererbt würden. Ich könnte mir vorstellen, dass bestimmte Vorlagenmagie schlecht reagiert.

Aber in meinem sehr einfachen Fall scheint es zu funktionieren.

Marsh Ray
quelle
16
In C ++ 11 können Sie Konstruktoren von using basename::basename;in der abgeleiteten Klasse erben , was bei komplizierten Ctoren kein Problem darstellt.
Xeo
1
Netter Trick, aber es funktioniert nicht, wenn der Zeiger auf IDontControl :: Nested im selben Header verwendet wird (wo er deklariert wurde) und über externen Code zugegriffen wird, der auch die vollständige Definition von IDontControl enthält. (Da der Compiler nicht mit IDontControl_Nested und IDontControl :: Nested übereinstimmt). Umgehung besteht darin, eine statische Umwandlung durchzuführen.
Artem Pisarenko
Ich würde empfehlen, das Gegenteil zu tun und die Klasse draußen zu haben und sie nur typedefinnerhalb der Klasse zu verwenden
ridderhoff
3

Wenn Sie wirklich vermeiden möchten, # die böse Header-Datei in Ihre Header-Datei aufzunehmen, können Sie dies tun:

HPP-Datei:

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

CPP-Datei

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

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

Aber dann:

  1. Sie müssen den eingebetteten Typ zum Zeitpunkt des Aufrufs angeben (insbesondere wenn Ihre Funktion keine Parameter des eingebetteten Typs akzeptiert).
  2. Ihre Funktion kann nicht virtuell sein (da es sich um eine Vorlage handelt).

Also, ja, Kompromisse ...

Edenbridge
quelle
1
Was zum Teufel ist eine hppDatei?
Naftali aka Neal
7
lol, eine .hpp- Headerdatei wird in C ++ - Projekten verwendet, um sie von einer C-Headerdatei zu unterscheiden, die normalerweise mit .h endet. Wenn Sie mit C ++ und C im selben Projekt arbeiten, bevorzugen einige Benutzer .hpp und .cpp für C ++ - Dateien, um explizit festzulegen, mit welchem ​​Dateityp sie sich befassen, und .h und .c für C-Dateien.
Bitek
2

Dies kann erfolgen, indem die äußere Klasse als Namespace vorwärts deklariert wird .

Beispiel: Wir müssen eine verschachtelte Klasse other :: A :: Nested in Others_a.h verwenden, die außerhalb unserer Kontrolle liegt.

Andere_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();
}
Bitlixi
quelle
1
Es mag funktionieren, ist aber nicht dokumentiert. Der Grund, warum es funktioniert, ist, dass a::bes auf die gleiche Weise entstellt wird, egal ob aKlasse oder Namespace.
Jaskmar
3
Funktioniert nicht mit Clang oder GCC. Es heißt, dass die äußere Klasse als etwas anderes als ein Namespace deklariert wurde.
Dugi
1

Ich würde dies keine Antwort nennen, aber dennoch einen interessanten Fund: Wenn Sie die Deklaration Ihrer Struktur in einem Namespace namens C wiederholen, ist alles in Ordnung (zumindest in gcc). Wenn die Klassendefinition von C gefunden wird, scheint sie den Namensraum C stillschweigend zu überschreiben.

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
quelle
1
Ich habe dies mit cygwin gcc versucht und es wird nicht kompiliert, wenn Sie versuchen, auf A.someField zu verweisen. C :: D in der Klasse A-Definition bezieht sich tatsächlich auf die (leere) Struktur im Namespace, nicht auf die Struktur in der Klasse C (übrigens wird dies in MSVC nicht kompiliert)
Dolphin
Es gibt den Fehler: "'Klasse C' als andere Art von Symbol neu
deklariert
9
Sieht aus wie ein GCC-Fehler. Es scheint zu glauben, dass ein Namespace-Name einen Klassennamen im selben Bereich verbergen kann.
Johannes Schaub - litb
0

Dies wäre eine Problemumgehung (zumindest für das in der Frage beschriebene Problem - nicht für das eigentliche Problem, dh wenn Sie keine Kontrolle über die Definition von haben 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
quelle
0

Wenn Sie Zugriff haben, um den Quellcode der Klassen C und D zu ändern, können Sie die Klasse D separat herausnehmen und in Klasse C ein Synonym dafür eingeben:

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
Suworow Iwan
quelle