web-dev-qa-db-fra.com

Déclaration en aval de types / classes imbriqués en C ++

Je suis récemment tombé 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 transmettre déclarer un type imbriqué, les erreurs suivantes entraînent une erreur de compilation.

class C::D;

Des idées?

184
Calmarius

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

211
Adam Rosenfield
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

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

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

Ma solution de contournement était:

class IDontControl_Nested; // Forward reference to distinct name.

Plus tard, quand j'ai pu 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 n’aurait valu la peine s’il existait des constructeurs compliqués ou d’autres fonctions membres spéciales dont l’héritage n’était pas fluide. J'imaginais que certains modèles de magie réagissaient mal.

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

31
Marsh Ray

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 faire ceci:

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 incorporé au moment de l'appel (surtout si votre fonction ne prend aucun paramètre du type incorporé)
  2. votre fonction ne peut pas être virtuelle (car c'est un template)

Alors, oui, des compromis ...

3
edenbridge

Cela peut être fait par avant déclarer la classe externe comme un espace de noms.

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

autres_a.h

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

ma_classe.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();
}
2
bitlixi

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 (au moins en gcc). Lorsque la définition de classe de C est trouvée, elle semble remplacer silencieusement l'espace de noms 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;
}
0
nschmidt

Si vous avez le droit de modifier le code source des classes C et D, vous pouvez utiliser la classe D séparément et entrer un synonyme pour cette classe dans la classe C:

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
0
Suvorov Ivan

Ce serait une solution de contournement (du moins pour le problème décrit dans la question - et non pour le problème réel, c’est-à-dire sans contrôle de 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
}
0
chtz