Déclaration directe des types / classes imbriqués en C ++

197

Je me suis récemment retrouvé coincé dans une situation comme celle-ci:

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

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

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

class A;

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

class C::D;

Des idées?

Calmarius
la source
6
Pourquoi avez-vous besoin de cela? Notez que vous pouvez transmettre déclarer s'il s'agit d'un membre de la même classe en cours de définition: class X {class Y; Y * a; }; classe X :: Y {};
Johannes Schaub - litb
Cette solution a fonctionné pour moi (espace de noms C {classe D;};): stackoverflow.com/questions/22389784/…
Albert Wiersch
J'ai trouvé un lien vers
bitlixi

Réponses:

224

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

Adam Rosenfield
la source
6
Merci d'avoir répondu. 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é?
Marsh Ray
61
Oh. Juste ce que je ne voulais pas que Google apparaisse. Merci quand même pour la réponse concise.
learnvst
19
Même chose ici ... quelqu'un sait pourquoi ce n'est pas possible? Il semble qu'il existe des cas d'utilisation valides, et ce manque empêche la cohérence de l'architecture dans certaines situations.
Maël Nison du
Vous pouvez utiliser un ami. Et mettez simplement un commentaire dans le sens où vous l'utilisez pour contourner le trou en C ++.
Erik Aronesty
3
Chaque fois que je rencontre de tels défauts inutiles dans cette langue ersatz, je suis déchiré entre rire et pleurer
SongWithoutWords
33
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

J'avais besoin d'une référence directe 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 serait probablement plus problématique qu'elle n'en vaut s'il y avait des constructeurs compliqués ou d'autres fonctions membres spéciales qui n'étaient pas héritées en douceur. Je pouvais imaginer que certains modèles de magie réagissaient mal.

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

Marsh Ray
la source
16
En C ++ 11, vous pouvez hériter des constructeurs using 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 le pointeur vers IDontControl :: Nested est utilisé dans le même en-tête (où il a été déclaré) et accessible à partir d'un code externe qui comprend également la définition complète d'IDontControl. (Parce que le compilateur ne correspondra pas à IDontControl_Nested et IDontControl :: Nested). La solution consiste à effectuer une conversion statique.
Artem Pisarenko
Je recommanderais de faire le contraire et d'avoir la classe à l'extérieur, et de l'utiliser typedefà l'intérieur de la classe
ridderhoff
3

Si vous voulez vraiment éviter # d'inclure le fichier d'en-tête désagréable 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 intégré au moment de l'appel (surtout si votre fonction ne prend aucun paramètre du type intégré)
  2. votre fonction ne peut pas être virtuelle (car c'est un modèle)

Alors, oui, compromis ...

edenbridge
la source
1
Qu'est-ce qu'un hppfichier?
Naftali aka Neal
7
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 faire explicitement avec quel type de fichiers ils traitent, et .h et .c pour les fichiers C.
bitek
2

Cela peut être fait en déclarant la classe externe comme un espace de noms .

Exemple: Nous devons utiliser une classe imbriquée others :: A :: Nested in others_a.h, qui est hors de notre contrôle.

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();
}
bitlixi
la source
1
Cela peut fonctionner, mais ce n'est pas documenté. La raison pour laquelle cela fonctionne est qu'il a::best modifié de la même manière, qu'il s'agisse de aclasse ou d'espace de noms.
jaskmar
3
Ne fonctionne pas avec Clang ou GCC. Il dit que la classe externe a été déclarée comme quelque chose de différent d'un espace de noms.
Dugi
1

Je n'appellerais pas cela une réponse, mais néanmoins une découverte intéressante: si vous répétez la déclaration de votre structure dans un espace de noms appelé C, tout va bien (en gcc au moins). Lorsque la définition de classe de C est trouvée, elle semble remplacer silencieusement 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;
}
nschmidt
la source
1
J'ai essayé cela avec cygwin gcc et il ne se compile pas si vous essayez de référencer A.someField. C :: D dans la définition de classe A fait en fait référence à la structure (vide) dans l'espace de noms, pas à la structure dans la classe C (BTW cela ne se compile pas dans MSVC)
Dolphin
Il donne l'erreur: "'classe C' redéclarée comme un autre type de symbole"
Calmarius
9
Ressemble à un bogue GCC. Il semble qu'un nom d'espace de noms puisse masquer un nom de classe dans la même portée.
Johannes Schaub - litb
0

Ce serait une solution de contournement (au moins pour le problème décrit dans la question - pas pour le problème réel, c'est-à-dire lorsqu'il n'a pas le 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
}
chtz
la source
0

Si vous avez accès pour modifier le code source des classes C et D, vous pouvez supprimer la classe D séparément et saisir un synonyme pour celle-ci dans la classe C:

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
Souvorov Ivan
la source