Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

classes interdépendantes : déclarer une classe sans la définir

21 réponses
Avatar
meow
J'ai deux classes, A et B, chacune d=E9pend de sous parties de
l'autre... et j'aimerai si possible =E9viter de multiplier mes arguments
templates. Y a t'il un moyen d'=E9crire quelque chose du genre :

class A; //! t'inqui=E8tes pas copain compilo, la d=E9finition vient plus
loin
class B{
// j'utilise des types d=E9finis dans A
};
class A { // tu vois j'ai pas menti !
// j'utilise des types d=E9finis dans B
}

Bon, ok, =E7a a pas l'air bien joli niveau design... Alors si en sus
vous aviez des conseils pour =E9viter ce genre d'horreur. Je vous
explique un peu mieux mon probl=E8me, histoire de pas parler dans le
vide :

Ma classe A est une structure de donn=E9e qui rend compte d'un objet
volumique (une triangulation 3D, donc un ensemble de tetraedres,
triangles, arretes et sommets avec les relations d'incidences qui vont
bien)
Ma classe B est une structure de donn=E9e qui rend compte d'un objet
surfacique (demi arete, pour qui connait (merci Mr Kettner) )
j'ai un objet a de type A et je veux construire son bord sous la forme
d'un objet b de type B. Pour cette construction, je vais avoir besoin
de stocker les "=E9chafaudages" de b dans a. Donc A doit connaitre B (ou
du moins certaines sous parties de A doivent connaitre certaines sous
parties de B)
Mais je veux aussi que les sommets de b gardent l'information de leur
origine dans a (grosso modo je veux que les sommets de b pointent sur
les sommets de a dont ils sont issus)... moralit=E9 B doit aussi
connaitre A.
.=2E.Je fais comment ? :D

Ce a quoi j'avais pens=E9 partant de l'observation qu'il =E9tait tr=E8s laid
d'avoir un objet qui contienne les "=E9chafaudages" d'un autre, c'=E9tait
d'avoir deux classes tr=E8s proches : A et AA, la premi=E8re vierge de
toute pernition de B, et la seconde qui ne serait utilis=E9e que le
temps de la construction d'un objet de type B. A ne "connaitrait"
ainsi personne, B connaitrait A et AA connaitrait =E0 la fois A et B.
Un objet aa de type AA serait initialis=E9e par copie d'un objet de
classe A dont elle stockerait les r=E9f=E9rences dont on a besoin dans les
=E9l=E9ments de la classe B, et stockerait les "=E9chafaudages" de b avant
d'etre d=E9truit, une fois b construit.
Je pense que ce qui m'a rebut=E9 c'est le fait que ma classe A est tr=E8s
grosse et que =E7a me g=E8ne de "gaspiller" de la place m=E9moire avec deux
objets quasi identiques en m=E9moire.

10 réponses

1 2 3
Avatar
Michel Decima
On Mon, 02 Jul 2007 04:26:48 -0700, meow :

Tu noteras que si un B contient un A, un A ne peut pas contenir de B.
Bein oui... Et c'est bien ce qui me désole.



Mais pour le coup, ça n'a rien à voir avec une restriction du
compilateur.

Si un B contient un A, il est forcément plus grand que le A.
Si un A contient un B, il est forcément plus grand que le B.
Du coup, si un A contient un B qui lui-même contient un A, l'objet A
est plus grand que lui-même.


Si A et B sont vide, ca devrait aller...



Avatar
Fabien LE LEZ
On Mon, 02 Jul 2007 04:26:48 -0700, meow :

Tu noteras que si un B contient un A, un A ne peut pas contenir de B.


Bein oui... Et c'est bien ce qui me désole.


Imagine le cas suivant :

class A
{
B b;
};

class B
{
A a;
};

Le constructeur de B appelle le constructeur de chaque élément. Il
appelle donc le constructeur (ou un des constructeurs) de A. Qui
lui-même appelle le constructeur (ou un des constructeurs) de B. Qui
lui-même etc.
Aucun objet A (ou B) n'est donc constructible en un temps fini.


Avatar
Loïc Joly


Si A et B sont vide, ca devrait aller...


sizeof(T) n'a pas le droit d'être nul en C++.

--
Loïc

Avatar
espie
In article <46890f0d$0$4084$,
Loïc Joly wrote:

Si A et B sont vide, ca devrait aller...


sizeof(T) n'a pas le droit d'être nul en C++.


Oui et non... il y a quand meme le `empty base class optimization'
qui fausse un peu les calculs de taille...


Avatar
Michel Decima


Si A et B sont vide, ca devrait aller...


sizeof(T) n'a pas le droit d'être nul en C++.


Certes. Mais je crois que c'est une restriction que Fabien avait
proposé d'ignorer.


Avatar
Fabien LE LEZ
On Mon, 02 Jul 2007 16:19:54 +0200, Michel Decima >:

Si A et B sont vide, ca devrait aller...


Mais pour le coup, A n'a aucune variable membre modifiable. Par
conséquent, on peut déclarer toutes ses fonctions "static" sans perte
de fonctionnalité.
Et du coup, B n'a plus aucune raison d'avoir un A comme membre,
puisqu'on peut remplacer tous les "a.f()" par des "A::f()".

En d'autres termes, la possibilité que A contienne B qui contient A
aurait bien un sens dans ce cas précis, mais n'apporterait aucune
fonctionnalité.

Avatar
JBB
On Mon, 02 Jul 2007 04:26:48 -0700, meow :

Tu noteras que si un B contient un A, un A ne peut pas contenir de B.
Bein oui... Et c'est bien ce qui me désole.



Imagine le cas suivant :

class A
{
B b;
};

class B
{
A a;
};

Le constructeur de B appelle le constructeur de chaque élément. Il
appelle donc le constructeur (ou un des constructeurs) de A. Qui
lui-même appelle le constructeur (ou un des constructeurs) de B. Qui
lui-même etc.
Aucun objet A (ou B) n'est donc constructible en un temps fini.

Et peut on dire qu'un objet A est constructible en un temps infini ? ( avec une pile de taille infinie)




Avatar
Sylvain
AG wrote on 02/07/2007 13:47:
class A;

class B{
A *a;
public :
B();
};

class A
{
int a;
public:
A(){a=0;};
};

B::B(void)
{
a = new A();
}



ici comme dans le post précédent, A ne dépends absolument pas de B !

l'écriture immédiate est donc:

class A {
int a;
public:
A(){a=0;};
};

class B{
A* pa; // possible
A& ra; // sous reserve d'un cst: B(const A& _a) : ra(_a) { ... }
A a; // possible aussi avec cst de A par défaut
public:
B();
};

Sylvain.

Avatar
James Kanze
On Jul 2, 6:09 pm, Fabien LE LEZ wrote:
On Mon, 02 Jul 2007 16:19:54 +0200, Michel Decima >:

Si A et B sont vide, ca devrait aller...


Mais pour le coup, A n'a aucune variable membre modifiable. Par
conséquent, on peut déclarer toutes ses fonctions "static" sans perte
de fonctionnalité.


Elle pourrait avoir une identité:-).

Sérieusement, j'ai du mal à voir la véritable difficulté. Comme
tu dis, si A contient un B, alors B ne peut pas contenir un A.
Indépendamment de la taille, il y a une récursion infinie dans
la définition. Donc, si A contient un B, on définit B avant de
définir A, et vice versa. Aucun vrai problème. Maintenant, A et
B peuvent s'utiliser l'un l'autre, réciproquement. Mais là, il
n'y a besoin de la définition de la classe que lors de
l'implémentation des fonctions membres. C-à-d dans des fichiers
sources, et non dans les en-têtes. La solution « classique »
fonctionne ici parfaitement :

fichier A.hh :
#ifndef A_HH
#define A_HH

#include "B.hh"

class A
{
// y compris le membre B...
} ;
#endif

fichier B.hh :
#ifndef B_HH
#define B_HH

class A ;

class B
{
// ...
} ;
#endif

fichier A.cc :

#include "A.hh"
#include "B.hh"

// ...

fichier B.cc :

#include "B.hh"
#include "A.hh"

// ...

Ce qui marche parfaitement tant qu'il n'y a pas de templates.
Si A et B sont des classes templatées, ça se corse, parce que
sans « export », il faut que les définitions des fonctions se
trouvent dans un en-tête aussi. Aussi, on pourrait imaginer des
structures récursives où A contient un std::vector< B > et
vice versa. Mais ça ne marche pas non plus, parce que la norme
ne permet l'instantiation d'une collection standard que sur un
type complet. Dans ce deuxième cas, l'utilisation des pointeurs
permet de contourner le problème, et serait probablement une
bonne idée même sans le problème -- les collections utilisent
des copies profondes, et des copies profondes des objets qui eux
aussi contiennent des vector, je n'aime pas trop.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
jeremie fouche

class B{
A* pa; // possible
A& ra; // sous reserve d'un cst: B(const A& _a) : ra(_a) { ... }


heuu y'a pas un pb de const là ?


--
Jérémie

1 2 3