Заголовки, включающие друг друга в С++

Я новичок в С++, но я не смог найти ответ на этот (скорее всего, тривиальный) вопрос в Интернете. У меня возникли проблемы с компиляцией кода, где два класса относятся друг к другу. Для начала, должны ли мои инструкции #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 # включать друг друга.