web-dev-qa-db-fra.com

fichiers d'en-tête c ++, y compris mutuellement

J'ai deux classes définies dans des fichiers d'en-tête séparés. Chaque fichier a un champ qui est le type d'une autre classe. Maintenant, j'ai inclus dans l'en-tête de chaque fichier l'en-tête d'un autre fichier, mais le compilateur génère des erreurs. Qu'est-ce que je rate?

39
MegaManX

Vous ne pouvez pas avoir chaque classe "un champ qui est le type d'une autre classe"; ce serait une définition récursive et non seulement le compilateur ne serait pas en mesure de donner un sens à cela, cela n'a même pas de sens logique.

Chaque classe ayant un champ qui est le type de l'autre classe est le genre d'impossibilité que vous ne voyez que dans M.C. Dessins Escher, ou animations de ceux-ci, comme celui-ci:

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

Source: escherdroste.math.leidenuniv.nl

basé sur la lithographie "Print Gallery" d'Escher, 1956, voir Wikipedia

L'un des deux champs devra être un pointeur , afin de briser le confinement récursif, et éviter l'impossibilité logique.

Ce qui nous amène au problème suivant: si la classe B doit contenir une instance de la classe A, alors évidemment, A doit être déclaré avant la classe B, de sorte que A est déjà connu du compilateur lors de la compilation de B. Mais si la classe A est déclaré avant la classe B, comment déclarer un pointeur vers B dans A? La classe B n'est pas encore connue au moment de la compilation de A! La réponse à cette question est une construction spéciale connue sous le nom de déclaration directe qui existe précisément afin de s'adapter à des situations comme celle-ci. Une déclaration avancée de classe B ressemble à ceci:

class B;

Tout ce qu'il dit au compilateur, c'est qu'il y aura une classe appelée B. Il ne dit rien au compilateur sur le contenu de la classe B, donc il y a très peu de choses que nous pouvons faire avec, mais nous pouvons faire une chose: déclarer des pointeurs vers B.

Ainsi, la solution complète au problème ressemble à ceci:

fichier "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; 
}

fichier "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;
}
53
Mike Nakis

Vous ne devez pas inclure les fichiers d'en-tête dans les autres, incluez simplement les fichiers d'en-tête dans vos fichiers source.

Dans les en-têtes, vous pouvez utiliser une déclaration directe:

// In Class1.h
class Class2;

// In class2.h
class Class1;

Vous pouvez également vous protéger contre l'inclusion d'un fichier deux fois à l'aide du préprocesseur:

// Class1.h
#ifndef __CLASS_1_H
#define __CLASS_1_H

// content

#endif
21
Matt Lacey

Je sais que c'est un vieux sujet mais peut-être êtes-vous toujours intéressé par une solution!

En fait, en C++, vous pouvez utiliser deux classes de manière récursive sans utiliser de pointeurs et voici comment procéder.

fichier: a.h

#include <b.h>

class A {
    B<> b;
}

fichier: b.h

class A;

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

fichier: main.cpp

#include "a.h"    
A a;

et c'est tout!

bien sûr c'est juste pour la curiosité :)

14
Boynux

Vous voudrez probablement utiliser la déclaration directe, à moins que vous ne vouliez réellement mettre une instance de chaque classe les unes dans les autres. Dans ce cas, vous ne devez rien utiliser.

3

Si B ne peut exister qu'au sein de A, il me semble que je peux créer A et B sans utiliser de pointeur. B doit simplement transmettre déclarer A et ne pas l'inclure (en évitant l'inclusion récursive).

Dans mon cas, un Document a un Section qui obtient une référence à son 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{};
}

Ce code se compile avec g++ et fonctionne sous Linux.

Un ensemble (complexe) de ifdef pourrait l'activer pour d'autres cas, mais je ne suis pas sûr de la lisibilité ...

0
a.l.e

Outre la possibilité de déclaration anticipée - s'il semble que vous ayez besoin de deux classes mutuellement l'une dans l'autre, c'est d'après mon expérience un signe d'erreur dans la profondeur de l'héritage. Les classes sont plutôt une sorte de frères et sœurs et vous devez créer une classe parent pour les deux. Ou vous essayez d'utiliser une classe qui est en fait une classe parente au sein d'une classe qui devrait avoir un frère de cette classe parente. Ensuite, vous devez créer ce frère en tant que troisième classe.

0
Thomas_M