Способ думать об этом - «думать как компилятор».
Представьте, что вы пишете компилятор. И вы видите такой код.
// 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;
}
Когда вы компилируете файл .cc (помните, что .cc, а не .h - это единица компиляции), вам нужно выделить место для объекта A
. Итак, сколько же места тогда? Хватит хранить B
! Каков размер B
тогда? Хватит хранить A
! К сожалению.
Ясно, что круговая ссылка, которую вы должны сломать.
Вы можете сломать его, позволив компилятору зарезервировать столько места, сколько ему известно о начальном этапе - например, указатели и ссылки всегда будут 32 или 64 битами (в зависимости от архитектуры), и поэтому, если вы заменили (любой из них) на указатель или ссылка, все было бы здорово. Допустим, мы заменим в A
:
// file: A.h
class A {
// both these are fine, so are various const versions of the same.
B& _b_ref;
B* _b_ptr;
};
Теперь все стало лучше. В некотором роде. main()
все еще говорит:
// file: main.cc
#include "A.h" // <-- Houston, we have a problem
#include
Для всех экстентов и целей (если вы удалите препроцессор) просто скопируйте файл в .cc . Так что на самом деле .cc выглядит так:
// file: partially_pre_processed_main.cc
class A {
B& _b_ref;
B* _b_ptr;
};
#include "B.h"
int main (...) {
A a;
}
Вы можете понять, почему компилятор не может справиться с этим - он понятия не имеет, что это B
такое - он никогда раньше не видел этот символ.
Итак, давайте расскажем компилятору о B
. Это известно как предварительная декларация и обсуждается далее в этом ответе .
// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
A a;
}
Это работает . Это не здорово . Но в этот момент у вас должно быть понимание проблемы циклических ссылок и того, что мы сделали, чтобы «исправить» ее, хотя это и исправление плохо.
Причина, по которой это исправление плохое, заключается в том, что следующий человек должен #include "A.h"
будет объявить, B
прежде чем сможет его использовать, и получит ужасную #include
ошибку. Так давайте перейдем к декларации в Ач сам.
// file: A.h
class B;
class A {
B* _b; // or any of the other variants.
};
И в Bh , на данный момент, вы можете просто #include "A.h"
напрямую.
// 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;
}
НТН.