La façon d'y penser est de "penser comme un compilateur".
Imaginez que vous écrivez 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 fichier .cc (n'oubliez pas que le .cc et non le .h est l'unité de compilation), vous devez allouer de l'espace pour l'objet A
. Alors, combien d'espace alors? Assez pour ranger B
! Quelle est la taille B
alors? Assez pour ranger A
! Oops.
Clairement une référence circulaire que vous devez briser.
Vous pouvez le casser en autorisant le compilateur à réserver à la place autant d'espace qu'il en sait sur les éléments initiaux - les pointeurs et les références, par exemple, seront toujours de 32 ou 64 bits (selon l'architecture) et donc si vous les avez remplacés (l'un ou l'autre) par un pointeur ou une référence, les choses seraient super. 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. Quelque peu. main()
dit toujours:
// file: main.cc
#include "A.h" // <-- Houston, we have a problem
#include
, à toutes fins utiles (si vous retirez le préprocesseur) copie simplement le fichier dans le .cc . Donc vraiment, 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 qui B
est - il n'a même jamais vu le symbole auparavant.
Parlons donc du compilateur B
. Ceci est connu comme une déclaration à terme , et est discuté plus loin dans cette réponse .
// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
A a;
}
Ça marche . Ce n'est pas génial . Mais à ce stade, vous devriez avoir une compréhension du problème des références circulaires et de ce que nous avons fait pour le "corriger", bien que la correction soit 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 obtiendra une terrible #include
erreur. Déplaçons donc la déclaration dans Ah lui-même.
// file: A.h
class B;
class A {
B* _b; // or any of the other variants.
};
Et à Bh , à ce stade, vous pouvez simplement #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.