Под "макросами" я предполагаю, что вы имеете в виду, что #ifndef включает охрану?
Если так, то #includes обязательно должны войти внутрь. Это одна из основных причин, по которой существуют охранники, потому что в противном случае вы легко получите бесконечную рекурсию, как заметили.
В любом случае, проблема в том, что в то время, когда вы используете классы A и B (внутри другого класса), они еще не были объявлены. Посмотрите, как выглядит код после обработки #include:
//#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;
}
Теперь прочитайте код. B - это первый класс, с которым сталкивается компилятор, и он включает A&
член. Что такое A
? Компилятор еще не определил A
, поэтому выдает ошибку.
Решение состоит в том, чтобы сделать предварительное объявление A. В какой-то момент перед определением B добавьте строку class A;
Это дает компилятору необходимую информацию о том, что A является классом. Мы еще ничего не знаем об этом, но так как B нужно только включить ссылку на него, это достаточно хорошо. В определении A нам нужен член типа B (не ссылка), поэтому здесь должно быть видно все определение B. К счастью, это так.