2009-06-04 10 views
164

Je suis récemment coincé dans une situation comme celle-ci:déclaration avant de types/classes imbriquées dans C++

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

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

Normalement, vous pouvez déclarer un nom de classe:

class A; 

Mais vous ne pouvez pas en avant déclarer un type imbriqué, ce qui suit provoque une erreur de compilation.

class C::D; 

Des idées?

+5

Pourquoi avez-vous besoin de cela? Notez que vous pouvez renvoyer déclarer si c'est un membre de la même classe en cours de définition: classe X {classe Y; Y * a; } classe X :: Y {}; –

+1

Erreur fascinante. –

+0

Cette solution a fonctionné pour moi (namespace C {classe D;};): http://stackoverflow.com/questions/22389784/c-code-fails-to-compile-after-upgrading-xcode-5-0-5 -1-forward-declaration –

Répondre

179

Vous ne pouvez pas le faire, c'est un trou dans le langage C++. Vous devrez démonter au moins l'une des classes imbriquées.

+5

Merci pour la réponse. Dans mon cas, ce ne sont pas mes classes imbriquées. J'espérais éviter une énorme dépendance de fichier d'en-tête de bibliothèque avec une petite référence vers l'avant. Je me demande si C++ 11 l'a corrigé? –

+46

Oh. Juste ce que je ne voulais pas que google apparaisse. Merci quand même pour la réponse concise. – learnvst

+15

Pareil ici ... est-ce que quelqu'un sait * pourquoi * ce n'est pas possible? Il semble qu'il y ait des cas d'utilisation valides, et ce manque empêche la cohérence de l'architecture dans certaines situations. –

0

Je n'appellerais pas cela une réponse, mais néanmoins une trouvaille intéressante: Si vous répétez la déclaration de votre struct dans un espace de nom appelé C, tout va bien (en gcc au moins). Lorsque la définition de la classe C se trouve, il semble écraser en silence le 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

J'ai essayé ceci avec cygwin gcc et il ne compile pas si vous essayez de faire référence à A.someField. C :: D dans la définition de classe fait référence à la structure (vide) dans l'espace de noms, pas à la structure dans la classe C (BTW ne compile pas dans MSVC) – Dolphin

+0

Il donne l'erreur: "'classe C 'redéclaré comme un type de symbole différent' – Calmarius

+7

Cela ressemble à un bug de GCC. Il semble penser qu'un nom d'espace de noms peut masquer un nom de classe dans la même portée. –

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

je besoin d'une référence en avant comme:

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

Ma solution était:

class IDontControl_Nested; // Forward reference to distinct name. 

Plus tard, quand je pourrais utiliser la définition complète:

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

Cette technique poserait probablement plus de problèmes que cela en valait la peine s'il existait des constructeurs compliqués ou d'autres fonctions membres spéciales héritées sans heurts. Je pourrais imaginer que certains modèles de magie réagissent mal.

Mais dans mon cas très simple, cela semble fonctionner.

+10

En C++ 11, vous pouvez hériter des constructeurs en 'utilisant basename :: basename;' dans la classe dérivée, donc pas de problème avec les cteurs compliqués. – Xeo

+1

Belle astuce, mais cela ne fonctionnera pas si pointeur vers IDontControl :: imbriqué utilisé dans le même en-tête (où il a déclaré en avant) et accédé à partir de code externe qui comprend également la définition complète de IDontControl. (Parce que le compilateur ne correspondra pas IDontControl_Nested et IDontControl :: Nested). La solution consiste à effectuer une conversion statique. –

3

Si vous voulez vraiment éviter #including le fichier d'en-tête méchant dans votre fichier d'en-tête, vous pouvez le faire:

fichier HPP:

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

fichier cpp

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

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

Mais alors:

  1. vous devrez spécifier le type embarqué à l'heure d'appel (surtout si votre fonction ne prend aucun paramètre de type embarqué)
  2. votre fonction ne peut pas être virtuelle (parce qu'il est un modèle)

Alors, oui, ... compromis

+1

Que diable est un fichier 'hpp'? – Neal

+6

lol, un fichier d'en-tête ** .hpp ** est utilisé dans les projets C++ pour le distinguer d'un fichier d'en-tête C qui se termine généralement par .h. Lorsque vous travaillez avec C++ et C dans le même projet, certaines personnes préfèrent .hpp et .cpp pour les fichiers C++, pour le rendre explicitement avec quel type de fichiers ils traitent, et .h et .c pour les fichiers C. – bitek

0

Ce serait une solution (au moins pour le problème décrit dans la question - non pas pour le problème réel, à savoir, lorsqu'il n'a pas de contrôle sur la définition de 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 
}