заголовочные файлы c ++, включая друг друга - PullRequest
37 голосов
/ 16 декабря 2011

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

Ответы [ 6 ]

52 голосов
/ 16 декабря 2011

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

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

based on Escher's

Источник: 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; 
}

файл "Б.ч":

#include "A.h"

class B
{
   /* the compiler now knows the size of "A", so we can have a field of type "A". */
   A a;
}
20 голосов
/ 16 декабря 2011

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

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

// In Class1.h
class Class2;

// In class2.h
class Class1;

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

// Class1.h
#ifndef __CLASS_1_H
#define __CLASS_1_H

// content

#endif
14 голосов
/ 09 сентября 2013

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

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

файл: ах

#include <b.h>

class A {
    B<> b;
}

файл: чч

class A;

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

файл: main.cpp

#include "a.h"    
A a;

и все!

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

2 голосов
/ 16 декабря 2011

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

0 голосов
/ 13 мая 2019

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

0 голосов
/ 03 мая 2019

Если 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 может включить его для других случаев, но я не уверен насчет читаемости ...

...