La façon de voir les choses est de "penser comme un compilateur".
Imaginez que vous êtes en train d'écrire un compilateur. Et vous voyez du code comme celui-ci.
// file: A.h
class A {
B _b;
};
// file: B.h
class B {
A _a;
};
// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
A a;
}
Lorsque vous compilez le .cc (rappelez-vous que le fichier .cc et non le .h est l'unité de compilation), vous devez allouer de l'espace pour l'objet A
. Alors, combien d'espace ? Assez pour stocker B
! Quelle est la taille de B
alors ? Assez pour stocker A
! Oups.
Il s'agit clairement d'une référence circulaire que vous devez briser.
Vous pouvez le casser en permettant au compilateur de réserver à la place autant d'espace qu'il le sait en amont - les pointeurs et les références, par exemple, seront toujours de 32 ou 64 bits (selon l'architecture) et donc si vous remplacez (l'un ou l'autre) par un pointeur ou une référence, les choses iraient bien. Disons que nous remplaçons dans A
:
// file: A.h
class A {
// both these are fine, so are various const versions of the same.
B& _b_ref;
B* _b_ptr;
};
Maintenant les choses vont mieux. Un peu. main()
dit encore :
// file: main.cc
#include "A.h" // <-- Houston, we have a problem
#include
qui, à toutes fins utiles (si vous retirez le préprocesseur), se contente de copier le fichier dans le répertoire de l'utilisateur. .cc . Donc, en réalité, le .cc ressemble :
// file: partially_pre_processed_main.cc
class A {
B& _b_ref;
B* _b_ptr;
};
#include "B.h"
int main (...) {
A a;
}
Vous pouvez voir pourquoi le compilateur ne peut pas gérer cela - il n'a aucune idée de ce que les B
est - il n'a jamais vu le symbole avant.
Alors disons au compilateur B
. C'est ce qu'on appelle un déclaration prospective et est discuté plus en détail dans cette réponse .
// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
A a;
}
Ce site travaux . Il n'est pas grand . Mais à ce stade, vous devriez avoir une idée du problème de la référence circulaire et de ce que nous avons fait pour le "corriger", même si la correction est mauvaise.
La raison pour laquelle ce correctif est mauvais est que la prochaine personne à #include "A.h"
devra déclarer B
avant de pouvoir l'utiliser et obtiendront une terrible #include
erreur. Déplaçons donc la déclaration dans A.h lui-même.
// file: A.h
class B;
class A {
B* _b; // or any of the other variants.
};
Et dans B.h à ce stade, vous pouvez juste #include "A.h"
directement.
// file: B.h
#include "A.h"
class B {
// note that this is cool because the compiler knows by this time
// how much space A will need.
A _a;
}
HTH.
23 votes
Lorsque l'on travaille avec Visual Studio, le /showIncludes aide beaucoup à déboguer ce genre de problèmes.