Заголовки, включающие друг друга в С++
Я новичок в С++, но я не смог найти ответ на этот (скорее всего, тривиальный) вопрос в Интернете. У меня возникли проблемы с компиляцией кода, где два класса относятся друг к другу. Для начала, должны ли мои инструкции #include перемещаться внутри или вне моих макросов? На практике это не имело значения. Однако в этом конкретном случае у меня возникают проблемы. Приведение операторов #include за пределы макросов заставляет компилятор рекурсивно и дает мне "слишком сложные вложенные" ошибки. Кажется, это имеет смысл для меня, поскольку ни один класс не был полностью определен до того, как был вызван вызов #include. Однако, как ни странно, когда я пытаюсь поместить их внутрь, я не могу объявить тип одного из классов, поскольку он не распознается. Вот, в сущности, то, что я пытаюсь скомпилировать:
хиджры
#ifndef A_H_
#define A_H_
#include "B.h"
class A
{
private:
B b;
public:
A() : b(*this) {}
};
#endif /*A_H_*/
B.h
#ifndef B_H_
#define B_H_
#include "A.h"
class B
{
private:
A& a;
public:
B(A& a) : a(a) {}
};
#endif /*B_H_*/
main.cpp
#include "A.h"
int main()
{
A a;
}
Если это имеет значение, я использую g++ 4.3.2.
И просто для того, чтобы быть ясным, в общем, куда следует включать заявления #include? Я всегда видел, как они выходят за пределы макросов, но сценарий, который я описал, как представляется, нарушает этот принцип. Спасибо любым помощникам заранее! Пожалуйста, позвольте мне уточнить мое намерение, если я совершу глупые ошибки!
Ответы
Ответ 1
Под "макросами" я предполагаю, что вы имеете в виду #ifndef include guard?
Если это так, #includes обязательно должен войти внутрь. Это одна из основных причин, почему включают охранников, потому что иначе вы легко закончите бесконечную рекурсию, как вы заметили.
В любом случае проблема заключается в том, что в то время, когда вы используете классы A и B (внутри другого класса), они еще не объявлены. Посмотрите, как выглядит код после завершения #includes:
//#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. Каково это, к счастью.
Ответ 2
И просто для того, чтобы быть ясным, в общем, куда следует включать заявления #include?
Внутри включенных охранников по указанным вами причинам.
Для вашей другой проблемы: вам нужно переслать-объявить хотя бы один из классов, например. например:
#ifndef B_H_
#define B_H_
// Instead of this:
//#include "A.h"
class A;
class B
{
private:
A& a;
public:
B(A& a) : a(a) {}
};
#endif /*B_H_*/
Это работает только для объявлений: как только вы действительно используете экземпляр A
, вам также необходимо его определить.
Кстати, что говорит Натан, это правда: вы не можете рекурсивно переводить экземпляры класса друг в друга. Это работает только с указателями (или, в вашем случае, ссылками) к экземплярам.
Ответ 3
Oops! Я думаю, что нашел решение, которое включает в себя включение операторов #include внутри класса и использование форвардного объявления. Итак, код выглядит так:
#ifndef A_H_
#define A_H_
class B;
#include "B.h"
class A
{
private:
B b;
public:
A() : b(*this) {}
};
#endif /*A_H_*/
И аналогично для класса B. Он компилируется, но является ли это лучшим подходом?
Ответ 4
В таких ситуациях я создаю общий заголовок, который должен быть включен во все источники с форвардными объявлениями:
#ifndef common_hpp
#define common_hpp
class A;
class B;
#endif
Тогда отдельным файлам заголовков классов обычно не требуется никаких #includes для ссылки на другие классы, если все, что вам нужно, это указатели или ссылки на эти классы. Половина времени, хотя в этих заголовках есть другие лакомства, но по крайней мере любая проблема с круговыми ссылками решается с помощью common.hpp
Ответ 5
Я сомневаюсь, что это можно сделать. Вы не говорите о том, чтобы вызывать две функции изнутри друг друга рекурсивно, а вместо того, чтобы переводить два объекта внутрь друг друга рекурсивно. Подумайте о размещении дома с фотографией дома с изображением дома и т.д. Это займет бесконечное пространство, потому что у вас будет бесконечное количество домов и картин.
Что вы можете сделать, это включить в каждый из A
и B
указатели или ссылки друг другу.
Ответ 6
Некоторые компиляторы (в том числе gcc) также поддерживают #pragma один раз, однако, "идион" "включить охранников" в ваш вопрос является обычной практикой.
Ответ 7
Зависимость между двумя классами в хорошем дизайне программного обеспечения может быть нарисована как дерево.
По этой причине С++ не позволит двум файлам .h # включать друг друга.