web-dev-qa-db-fra.com

En-têtes inclus entre eux en C++

Je suis un débutant en C++, mais je n'ai pas trouvé de réponse en ligne à cette question (probablement triviale). Je ne parviens pas à compiler du code dans lequel deux classes s’incluent mutuellement. Pour commencer, mes instructions #include doivent-elles aller à l'intérieur ou à l'extérieur de mes macros? En pratique, cela n'a pas semblé avoir d'importance. Cependant, dans ce cas particulier, j'ai des problèmes. Placer les instructions #include en dehors des macros provoque la recurse du compilateur et me donne des erreurs "#include imbriqué trop profondément". Cela me semble logique, car aucune classe n’a été entièrement définie avant que #include n’ait été invoqué. Cependant, étrangement, lorsque je tente de les insérer à l'intérieur, je suis incapable de déclarer un type de l'une des classes, car il n'est pas reconnu. Voici, en substance, ce que j'essaie de compiler:

A.h

#ifndef A_H_
#define A_H_

#include "B.h"

class A
{
    private:
        B b;

    public:
        A() : b(*this) {}
};

#endif /*A_H_*/

B.h

#ifndef B_H_
#define B_H_

#include "A.h"

class B
{
    private:
            A& a;

    public:
        B(A& a) : a(a) {}
 };

#endif /*B_H_*/

main.cpp

#include "A.h"

int main()
{
    A a;
}

Si cela fait une différence, j'utilise g ++ 4.3.2.

Et juste pour être clair, en général, où les déclarations #include devraient-elles aller? Je les ai toujours vus sortir des macros, mais le scénario que j'ai décrit semble clairement enfreindre ce principe. Merci à tous les aides à l'avance! Permettez-moi de clarifier mon intention si j'ai commis des erreurs stupides!

40
Scott

Par "les macros", je suppose que vous voulez dire que les #ifndef incluent des gardes? Si c'est le cas, #includes devrait absolument aller à l'intérieur. C'est l'une des principales raisons pour lesquelles inclure des gardes existe, car sinon vous vous retrouvez facilement avec une récursion infinie, comme vous l'avez remarqué.

Quoi qu’il en soit, le problème est qu’au moment où vous utilisez les classes A et B (dans l’autre classe), elles n’ont pas encore été déclarées. Regardez à quoi ressemble le code après le traitement de #includes:

//#include "A.h" start
#ifndef A_H_
#define A_H_

//#include "B.h" start
#ifndef B_H_
#define B_H_

//#include "A.h" start
#ifndef A_H_ // A_H_ is already defined, so the contents of the file are skipped at this point
#endif /*A_H_*/

//#include "A.h" end

class B
{
    private:
            A& a;

    public:
            B(A& a) : a(a) {}
 };

#endif /*B_H_*/

//#include "B.h" end

class A
{
    private:
            B b;

    public:
            A() : b(*this) {}
};

#endif /*A_H_*/
//#include "A.h" end

int main()
{
    A a;
}

Maintenant, lisez le code. B est la première classe rencontrée par le compilateur et comprend un membre A&. Qu'est-ce que A? Le compilateur n'a pas encore rencontré de définition de A, il génère donc une erreur.

La solution consiste à faire une déclaration en aval de A. À un moment donné avant la définition de B, ajoutez une ligne class A;

Cela donne au compilateur les informations nécessaires, à savoir que A est une classe. Nous ne savons pas encore plus quoi que ce soit à ce sujet, mais comme B n’a besoin que d’y faire référence, c’est suffisant. Dans la définition de A, il faut un membre de type B (pas une référence), donc ici toute la définition de B doit être visible. Ce que c'est, heureusement.

64
jalf

Et juste pour être clair, en général, où les déclarations #include devraient-elles aller?

Dans les gardes d'inclusion, pour la raison que vous avez mentionnée.

Pour votre autre problème: vous devez transmettre-déclarer au moins une des classes, par exemple comme ça:

#ifndef B_H_
#define B_H_

// Instead of this:
//#include "A.h"

class A;

class B
{
    private:
            A& a;

    public:
            B(A& a) : a(a) {}
 };

#endif /*B_H_*/

Cela ne fonctionne cependant que pour les déclarations: dès que vous utilisez réellement une instance de A, vous devez également l'avoir définie.

Au fait, ce que Nathan dit est vrai: vous ne pouvez pas mettre des instances de classe les unes dans les autres de manière récursive. Cela ne fonctionne qu'avec pointers (ou, dans votre cas, des références) à des instances.

14
Konrad Rudolph

Oops! Je pense avoir trouvé une solution qui implique de placer les instructions #include dans la classe et d'utiliser une déclaration forward. Ainsi, le code ressemble à:

#ifndef A_H_
#define A_H_

class B;

#include "B.h"

class A
{
    private:
            B b;

    public:
            A() : b(*this) {}
};

#endif /*A_H_*/

Et de même pour la classe B. Cela compile, mais est-ce la meilleure approche?

4
Scott

Dans de telles situations, je crée un en-tête commun à inclure dans toutes les sources avec les déclarations en aval:

#ifndef common_hpp
#define common_hpp

class A;
class B;

#endif

Ensuite, les fichiers d'en-tête de classe individuels n'ont généralement pas besoin de #includes pour faire référence à d'autres classes, si tout ce dont vous avez besoin est de pointeurs ou de références à ces classes. La moitié du temps, bien qu'il y ait d'autres goodies dans ces en-têtes, mais au moins tout problème avec les références circulaires est résolu avec common.hpp

2
DarenW

Je doute que cela puisse être fait. Vous ne parlez pas d'appeler récursivement deux fonctions de l'intérieur, mais plutôt de mettre récursivement deux objets l'un dans l'autre. Pensez à mettre une maison avec une photo d'une maison avec une photo d'une maison, etc. Cela prendra une quantité infinie d'espace car vous aurez un nombre infini de maisons et de photos.

Ce que vous pouvez faire, c'est que chacun des A et B inclue des pointeurs ou des références l'un à l'autre.

0
Nathan Fellman

La dépendance entre deux classes dans une bonne conception logicielle peut être dessinée sous forme d'arborescence. 

Pour cette raison, C++ ne laissera pas deux fichiers .h s'inclure l'un à l'autre.

0
Alex Spencer

Certains compilateurs (y compris gcc) supportent également #pragma une fois mais l'idiome 'inclure les gardes' dans votre question est la pratique habituelle.

0
frankodwyer