Я протестировал следующий код (просто для проверки, я знаю, что это плохая идея), и я не мог понять, почему в нем ошибка. Пожалуйста, помогите мне!
Есть два класса A
и B
, оба в отдельных hpp
файлах. Я позволил им включать друг друга (я думаю, это не имеет значения из-за include guard
), и это не пошло не так, пока я не добавил определение B::fun
в B.cpp
.
А.ч
#ifndef A_H
#define A_H
#include "B.h"
class A {
friend void B::fun(A&);
};
#endif
Б.ч
#ifndef B_H
#define B_H
#include "A.h"
class A;
class B {
public:
void fun(A&);
};
#endif
B.cpp
#include "B.h"
void B::fun(A& a) {
}
main.cpp
#include "A.h"
#include "B.h"
int main() {
}
Я запускаю его с помощью такой команды:
g++ -g -o test main.cpp B.cpp
Сообщение об ошибке
A.h:7:17: error: 'B' has not been declared
friend void B::fun(A&);
Я знаю два обходных пути для устранения симптомов, но я хочу понять основную причину проблемы.
Способ 1:
прокомментируйте код #include "A.h"
в B.h
Способ 2:
Измените #include "B.h"
на #include "A.h"
в B.cpp
Я хочу знать, почему эти обходные пути избегают симптомов, точную причину обоих методов.
Первый «обходной путь» - это путь (фактически удалить вместо комментария ;-)). Второй — это просто обходной путь, позволяющий снова появиться проблеме с дополнительными файлами.
Просто удалите #include "A.hpp"
из B.hpp
. Это происходит потому, что когда B.cpp
включает B.hpp
, последний, в свою очередь, включает A.hpp
до того, как B
будет определено. Но вам вообще не нужно включать A.hpp
, потому что вы правильно объявили class A
.
Вы не просите решения, вы спрашиваете причины, по которым ваши два обходных пути устраняют ошибку, хороший способ обучения. Мое почтение.
Чтобы понять, почему вы получаете ошибку и почему вы можете избавиться от нее двумя способами, которые вы нашли сами, вам следует «поиграть в препроцессор» и сделать включение самостоятельно, наблюдая при этом за действием ваших ifdef
-ов, они же охранники повторного включения.
Глядя на то, как компилируется B.cpp, вставьте заголовочные файлы туда, где вы делаете включения.
Вы получаете:
#ifndef B_H
#define B_H
#ifndef A_H
#define A_H
#include "B.h" /* not expanded, will be removed next step */
class A {
friend void B::fun(A&);
};
#endif
class A;
class B {
public:
void fun(A&);
};
#endif
void B::fun(A& a) {
}
Затем удалите те части, которым мешают #ifdef
s, то есть во второй раз заголовка B вы получите:
#ifndef B_H
#define B_H
#ifndef A_H
#define A_H
class A {
friend void B::fun(A&); /* obviously unknown B */
};
#endif
class A;
class B {
public:
void fun(A&);
};
#endif
void B::fun(A& a) {
}
Теперь вы понимаете, почему вы получаете ошибку в B.cpp.
Теперь, почему метод 1 работает?
Давайте сделаем то же самое снова, применив это изменение:
#ifndef B_H
#define B_H
/* #include "A.h" */ /* method 1 */
class A;
class B {
public:
void fun(A&); /* fine, because forward declaration three lines above is sufficient */
};
#endif
void B::fun(A& a) {
}
Теперь, почему метод 2 помогает?
/* method 2, include A.h before B.h into B.cpp */
#ifndef A_H
#define A_H
#ifndef B_H
#define B_H
/* #include "A.h" not expanded because of ifdef */
class A;
class B {
public:
void fun(A&); /* fine, because of forward declaration */
};
#endif
class A {
friend void B::fun(A&); /* fine of forward declaration B having been seen */
};
#endif
/* #include "B.h" not expanded, because of ifdef */
void B::fun(A& a) {
}
Короче говоря, оба обходных пути работают, делая предварительное объявление видимым перед использованием A
и полностью объявляя B
перед использованием.
Внезапно я подумал о другом вопросе. main.cpp -> main.obj и B.cpp -> B.obj, и оба они имеют класс B. Почему при их связывании нет ошибки: множественное определение B? Я буду признателен, если вы расскажете мне об этом.
Интересный вопрос. Пожалуйста, используйте кнопку "Задать вопрос", чтобы создать его.
Чтобы понять, откуда берется ошибка, просто следуйте «цепочке включений», которые происходят при компиляции B.cpp
:
B.cpp
включает B.h
.B.h
включает A.h
.A.h
пытается включить B.h
, но не делает этого, потому что вы правильно настроили защиту включения.friend void B::fun(A&);
в A.h
, но на данном этапе B
не объявлен!
Я отредактировал, чтобы подчеркнуть разницу между «знанием решения» и «наличием обходных путей без понимания». Я думаю, что это делает вопрос более ясным. Если вам это не нравится, не стесняйтесь отменить мою правку, и я оставлю ее. Удачи.