Заголовки, включающие друг друга в C ++ - PullRequest
43 голосов
/ 28 декабря 2008

Я новичок в C ++, но я не смог найти ответ на этот (скорее всего, тривиальный) вопрос онлайн. У меня возникли проблемы при компиляции кода, где два класса включают друг друга. Для начала, должны ли мои операторы #include идти внутри или вне моих макросов? На практике это не имеет значения. Однако в данном конкретном случае у меня возникают проблемы. Размещение операторов #include вне макросов приводит к тому, что компилятор возвращается, и выдает мне ошибки "#include nested too глубоко". Мне кажется, это имеет смысл, поскольку ни один класс не был полностью определен до вызова #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? Я всегда видел, как они выходят за пределы макросов, но сценарий, который я описал, явно нарушает этот принцип. Заранее спасибо всем помощникам! Пожалуйста, позвольте мне уточнить мои намерения, если я допустил глупые ошибки!

Ответы [ 7 ]

67 голосов
/ 28 декабря 2008

Под "макросами" я предполагаю, что вы имеете в виду, что #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. К счастью, это так.

14 голосов
/ 28 декабря 2008

И для ясности, в общем, куда должны идти операторы #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, вам необходимо определить его также.

Кстати, то, что говорит Натан, правда: вы не можете рекурсивно помещать экземпляры классов друг в друга. Это работает только с указателями (или, в вашем случае, ссылками) на экземпляры.

4 голосов
/ 28 декабря 2008

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. Он компилируется, но разве это лучший подход?

2 голосов
/ 31 декабря 2008

В таких ситуациях я создаю общий заголовок для включения во все источники с предварительными декларациями:

#ifndef common_hpp
#define common_hpp

class A;
class B;

#endif

Тогда отдельные заголовочные файлы классов обычно не нуждаются в #include для ссылки на другие классы, если все, что нужно, это указатели или ссылки на эти классы. Половина времени, хотя в этих заголовках есть и другие полезности, но, по крайней мере, любая проблема с циклическими ссылками решается с помощью common.hpp

0 голосов
/ 18 ноября 2014

Зависимость между двумя классами в хорошем дизайне программного обеспечения может быть нарисована в виде дерева.

По этой причине C ++ не позволяет двум .h файлам # включать друг друга.

0 голосов
/ 28 декабря 2008

Некоторые компиляторы (вкл. Gcc) также поддерживают # прагму один раз , однако идиома «включить охранников» в ваш вопрос - обычная практика.

0 голосов
/ 28 декабря 2008

Я сомневаюсь, что это можно сделать. Вы не говорите о рекурсивном вызове двух функций изнутри друг друга, а скорее о рекурсивном размещении двух объектов один внутри другого. Подумайте о том, чтобы поставить дом с изображением дома с изображением дома и т. Д. ... Это займет бесконечное количество места, потому что у вас будет бесконечное количество домов и фотографий.

То, что вы можете сделать, это иметь в каждом из A и B указатели или ссылки друг на друга.

...