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

c++ recursion header include

39545 просмотра

7 ответа

Я новичок в 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_*/

Bh

#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? Я всегда видел, как они выходят за пределы макросов, но сценарий, который я описал, явно нарушает этот принцип. Заранее спасибо всем помощникам! Пожалуйста, позвольте мне уточнить мои намерения, если я сделал какие-то глупые ошибки!

Автор: Scott Источник Размещён: 22.10.2019 07:36

Ответы (7)


67 плюса

Решение

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

Автор: jalf Размещён: 28.12.2008 11:24

14 плюса

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

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

Автор: Konrad Rudolph Размещён: 28.12.2008 11:20

4 плюса

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

Автор: Scott Размещён: 28.12.2008 11:23

3 плюса

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

#ifndef common_hpp
#define common_hpp

class A;
class B;

#endif

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

Автор: DarenW Размещён: 30.12.2008 09:03

0 плюса

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

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

Автор: Nathan Fellman Размещён: 28.12.2008 11:18

0 плюса

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

Автор: frankodwyer Размещён: 28.12.2008 12:20

0 плюса

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

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

Автор: Alex Spencer Размещён: 18.11.2014 03:24
Вопросы из категории :
32x32