Подтвердить что ты не робот

Файлы заголовков С++, включая друг друга, взаимно

У меня есть два класса, которые определены в отдельных файлах заголовков. Каждый файл имеет поле, которое является типом другого класса. Теперь я включил в заголовок каждого файла заголовок другого файла, но компилятор генерирует ошибки. Что мне не хватает?

4b9b3361

Ответ 1

Вы не можете иметь каждый класс иметь "поле, которое является типом другого класса"; это было бы рекурсивным определением, и не только компилятор не смог бы извлечь из него никакого смысла, он даже не имеет логического смысла.

Каждый класс, имеющий поле, являющееся типом другого класса, является видом невозможности, который вы видите только в M.C. Рисунки Эшера или их анимации, например:

                                            based on Escher's "Print Gallery" Lithograph, 1956

               Б. де Смит и Х. В. Ленстра - Источник: escherdroste.math.leidenuniv.nl

                     по мотивам литографии Эшера "Галерея печати", 1956 год, см. википедию

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

Что приводит нас к следующей проблеме: если класс B должен содержать экземпляр класса A, то, очевидно, A должен быть объявлен перед классом B, так что A уже известен компилятору при компиляции B. Но если класс A объявленный до класса B, как мы можем объявить указатель на B в A? Класс B еще не известен во время компиляции A! Ответом на это является специальная конструкция, известная как предварительное объявление, которая существует именно для того, чтобы приспособиться к подобным ситуациям. Предварительное объявление класса B выглядит следующим образом:

class B;

Все, что говорит компилятору, - это то, что будет класс с именем B. Он ничего не говорит компилятору о содержимом класса B, поэтому мы можем сделать с ним очень мало, но мы можем сделать одну вещь: объявить указатели на B.

Итак, полное решение проблемы выглядит следующим образом:

файл "A.h":

/* This is called a "forward declaration".  We use it to tell the compiler that the 
   identifier "B" will from now on stand for a class, and this class will be defined 
   later.  We will not be able to make any use of "B" before it has been defined, but 
   we will at least be able to declare pointers to it. */
class B;

class A
{
    /* We cannot have a field of type "B" here, because it has not been defined yet. 
       However, with the forward declaration we have told the compiler that "B" is a 
       class, so we can at least have a field which is a pointer to "B". */
    B* pb; 
}

файл "B.h":

#include "A.h"

class B
{
   /* the compiler now knows the size of "A", so we can have a field of type "A". */
   A a;
}

Ответ 2

Вы не должны включать файлы заголовков внутри других, просто включайте файлы заголовков в исходные файлы.

В заголовках вы можете использовать форвардное объявление:

// In Class1.h
class Class2;

// In class2.h
class Class1;

Также вы можете защитить от файла, который дважды включается с помощью препроцессора:

// Class1.h
#ifndef __CLASS_1_H
#define __CLASS_1_H

// content

#endif

Ответ 3

Я знаю, что это старая тема, но, возможно, вас все еще интересует решение!

На самом деле в С++ вы можете использовать два класса рекурсивно, не используя указатели, и вот как это сделать.

file: a.h

#include <b.h>

class A {
    B<> b;
}

file: b.h

class A;

template<typename T = A>
class B {
    T a;
}

file: main.cpp

#include "a.h"    
A a;

и что все!

конечно, это просто для любопытства:)

Ответ 4

Вероятно, вы хотите использовать форвардное объявление, если вы действительно не хотите ставить экземпляр каждого класса друг в друга. В этом случае вы ничего не должны использовать.

Ответ 5

Если B может существовать только внутри A, я могу создать A и B без использования указателя. B должен просто объявить A и не включать его (избегая рекурсивного включения).

В моем случае у Document есть Section который получает ссылку на свой Document.

section.h

class Document;

class Section
{
    public:
        Section(Document& document) : document{document} {} 
    private:
        Document& document;
};

document.h

#include "section.h"

class Document
{
    public:
        Document() : section{*this} {}
    private:
        Section section;
};

main.cpp

#include "document.h"

int main()
{
    Document document{};
}

Этот код компилируется с g++ и работает в Linux.

(Сложный) набор ifdef может включить его для других случаев, но я не уверен насчет читаемости...

Ответ 6

Помимо возможности заблаговременного объявления - если вам кажется, что вам нужно два класса взаимно в другом, это, по моему опыту, признак ошибки в глубине наследования. Скорее классы являются своего рода братьями и сестрами, и вы должны создать родительский класс для обоих. Или вы пытаетесь использовать класс, который на самом деле является родительским классом внутри класса, у которого должен быть брат этого родительского класса. Тогда вы должны создать этого родного брата как третий класс.