57 votes

les fichiers d'en-tête c++ se comprennent mutuellement

J'ai deux classes définies dans des fichiers d'en-tête distincts. Chaque fichier possède un champ qui correspond au type de l'autre classe. J'ai inclus dans l'en-tête de chaque fichier l'en-tête de l'autre fichier, mais le compilateur génère des erreurs. Qu'est-ce qui m'échappe ?

103voto

Mike Nakis Points 7259

Il n'est pas possible que chaque classe ait "un champ qui est le type d'une autre classe" ; il s'agirait d'une définition récursive et non seulement le compilateur ne serait pas en mesure d'en tirer le moindre sens, mais cela n'aurait même pas de sens sur le plan logique.

Chaque classe ayant un champ qui est un type de l'autre classe est le genre d'impossibilité que l'on ne voit que dans les dessins de M.C. Escher, ou dans les animations de ceux-ci, comme celle-ci :

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

B. de Smit et H. W. Lenstra - Source : escherdroste.math.leidenuniv.nl

d'après la lithographie "Print Gallery" d'Escher, 1956, voir Wikipédia

L'un des deux champs doit être un pointeur afin de briser le confinement récursif et d'é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, il est évident que A doit être déclarée avant la classe B, de sorte que A soit déjà connu du compilateur lors de la compilation de B. Mais si la classe A est déclarée avant la classe B, comment pouvons-nous déclarer un pointeur sur B dans A ? La classe B n'est pas encore connue au moment où A est compilée ! La réponse à cette question est une construction spéciale connue sous le nom de déclaration préalable qui existe précisément pour répondre à des situations comme celle-ci. Une déclaration préalable de classe B se présente comme suit :

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éclarent des pointeurs sur B.

La solution complète du problème se présente donc comme suit :

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 yet been
       defined. 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;
}

22voto

LaceySnr Points 6304

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

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

// In Class1.h
class Class2;

// In class2.h
class Class1;

Vous pouvez également éviter qu'un fichier soit inclus deux fois à l'aide du préprocesseur :

// Class1.h
#ifndef __CLASS_1_H
#define __CLASS_1_H

// content

#endif

7 votes

REMARQUE : les déclarations avancées signifient que vous ne pouvez utiliser cette classe que comme un pointeur dans le fichier d'en-tête. Pas comme instance de classe.

1 votes

Vous pouvez également l'utiliser comme type de retour ou type de paramètre dans la déclaration des fonctions et comme partie d'un type de référence.

0 votes

Vous ne devez pas utiliser de noms réservés pour les gardes d'inclusion (ni pour quoi que ce soit d'autre d'ailleurs), sinon vous risquez de rencontrer des problèmes tels que cette .

14voto

Boynux Points 787

Je sais qu'il s'agit d'un vieux sujet, mais la solution vous intéresse peut-être encore !

En fait, en C++, il est possible d'utiliser deux classes de manière récursive sans utiliser de pointeurs.

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, ce n'est qu'une question de curiosité :)

2 votes

Avez-vous essayé de le compiler ? MSVC me donne l'erreur : "B<A>::a" uses an undefined class "A"

0 votes

BTW, ceci n'est pas destiné à être utilisé dans les codes de production. C'est juste pour s'amuser et c'est pourquoi ce n'est pas marqué comme réponse.

3voto

Il est probablement préférable d'utiliser la déclaration directe, à moins que vous ne souhaitiez réellement placer des instances de chaque classe l'une dans l'autre. Dans ce cas, vous ne devriez rien utiliser.

1voto

a.l.e Points 547

Si B ne peut exister qu'à l'intérieur de A, il semble que je puisse créer A et B sans utiliser de pointeur. B doit simplement déclarer A et ne pas l'inclure (évitant ainsi l'inclusion récursive).

Dans mon cas, un Document a une 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é...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X