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
AG
class A; //! t'inquiètes pas copain compilo, la définition vient
plusloin
class B{
// j'utilise des types définis dans A
};
class A { // tu vois j'ai pas menti !
// j'utilise des types définis dans B
}

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


Je pense que c'est la manière traditionnelle de faire. Aussi laide
soit-elle.

Avatar
Sylvain
meow wrote on 29/06/2007 17:39:
J'ai deux classes, A et B, chacune dépend de sous parties de
l'autre... et j'aimerai si possible éviter de multiplier mes arguments
templates. Y a t'il un moyen d'écrire quelque chose du genre :

class A;
class B{
// j'utilise des types définis dans A
^^^^^ plus sûrement des méthodes / propriétés.

};
class A { // tu vois j'ai pas menti !
// j'utilise des types définis dans B
^^^^^ idem

}

Bon, ok, ça a pas l'air bien joli niveau design... Alors si en sus
vous aviez des conseils pour éviter ce genre d'horreur.


une déclaration avec définition différée n'a rien de très laid; si de
plus les classes si définies conjointement, la relecture n'est pas gênée
par cela.

[...]
Je pense que ce qui m'a rebuté c'est le fait que ma classe A est très
grosse et que ça me gène de "gaspiller" de la place mémoire avec deux
objets quasi identiques en mémoire.


une alternative à votre approche en, est effet, de ne déclarer qu'une
seule et unique classe qui contienne toutes les informations nécessaires
à la construction des 2 ensembles de données avec 2 jeux de méthodes
d'utilisation selon le besoin externe; on pourrait pour faciliter cet
usage définir 2 classes de "travail pur", entièrement initialisée à
partir de la première et non à même de générer elle même les points /
grandeurs caractéristiques (pour ne pas dupliquer cette partie qui est
le corps de votre interdépendance actuelle).

Sylvain.

Avatar
meow
On doit la se comprendre... En fait j'ai deux problèmes, le premier
technique, et le second plutot design.
Commençons par le premier :

class A;

class B{
A a;
public:
B(A aa):a(aa){}
A& get () {return a;}
};

class A{
int i;
public:
A(int I):i(I){}
int get () {return i;}
};

int main () {
B b(A(15));
std::cout<<b.get().get()<<std::endl;
}

ne compile pas :

/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp:6:
error: field 'a' has incomplete type
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp: In
constructor 'B::B(A)':
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp:8:
error: 'aa' has incomplete type
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp:3:
error: forward declaration of 'struct A'
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp:8:
error: class 'B' does not have any field named 'a'
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp: In
constructor 'B::B(A)':
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp:8:
error: 'aa' has incomplete type
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp:3:
error: forward declaration of 'struct A'
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp: In
constructor 'B::B(A)':
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp:8:
error: 'aa' has incomplete type
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp:3:
error: forward declaration of 'struct A'
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp: In
member function 'A& B::get()':
/home/schwarz/Bacasable/workspace/Exemples/Class_forward_def.cpp:9:
error: 'a' was not declared in this scope

d'où ma première question : comment on fait ce genre de choses ?
Avatar
Fabien LE LEZ
On Mon, 02 Jul 2007 01:37:29 -0700, meow :

B(A aa):a(aa){}


On passe généralement les arguments par référence constante (sauf pour
les types de base, comme int et les pointeurs).

B (A const& a_) : a (a_) {}

Par ailleurs, ton B contient un A. Ce qui signifie que A doit être
définie avant B :

class B;

class A
{
// A peut contenir des B&, des B*, mais pas de B
};

class B
{
// B peut contenir un A.
};

Si tu définis les contenus avant les contenants, tu n'auras aucun
souci.

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

Avatar
Jean-Marc Bourguet
meow writes:

On doit la se comprendre... En fait j'ai deux problèmes, le premier
technique, et le second plutot design.
Commençons par le premier :

class A;

class B{
A a;
public:
B(A aa):a(aa){}
A& get () {return a;}
};

class A{
int i;
public:
A(int I):i(I){}
int get () {return i;}
};

int main () {
B b(A(15));
std::cout<<b.get().get()<<std::endl;
}

ne compile pas :


Pour avoir un membre d'un type donne, il faut avoir vu la definition (et
pas uniquement la declaration) du type. Ici quand tu definis B, tu n'as vu
qu'une declaration de A. (Un exemple de problemes de l'absence de
definition: comment calculer la taille de B si on ne connait pas celle de A?)

La declaration de la destination suffit pour des pointeurs et des
references, elle suffit aussi pour la declaration de fonctions ayant ce
type en parametre.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
meow
La réponse est donc : "c'est pas possible dirrectement, il faut passer
par des pointeurs". Merci.
Merci aussi pour les remarques sur mon code, et oui, c'est pas parce
qu'on fait une exemple rapide qu'il faut le faire laidement ;)
Avatar
meow
Après quelques essais supplémentaires...

La declaration de la destination suffit pour des pointeurs et des
references, elle suffit aussi pour la declaration de fonctions ayant ce
type en parametre.


Et encore... Je n'ai visiblement pas le droit de faire grand chose
avec de tels pointeurs, ce qui en définitive me parrait logique.
J'explicite :

class A;

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

Rien que ça, ça va déjà poser problème parce qu'à ce moment le compilo
n'a aucun moyen de savoir si j'ai bien un tel constructeur dans A...

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.
En fait j'ai l'impression que si le compilo mettait ces cas de figure
de coté jusqu'à trouver la définition de A il aurrait l'occasion de
faire les liens à ce moment, ce qui permettrait d'avoir des
définitions récursives... Mais j'imagine qu'il doit y avoir des soucis
que je ne vois pas :)

Concrètement maintenant, si je veux m'en sortir il semblerait qu'une
méthode pourrait consister dans l'emploi d'un héritage virtuel.
Genre une classe abstraite AA déclarant les fonctions virtuelles dont
j'aurai besoin dans B, une classe BB déclarant les fonctions dont
j'aurai besoni dans A, puis des classes A et B descendant
respectivement de AA et BB et contenant respectivement un BB* et un
AA*
...J'ai bon ?

Avatar
AG
class A;

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

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

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

chez moi ça marche.
Avatar
Fabien LE LEZ
On Mon, 02 Jul 2007 04:26:48 -0700, meow :

class A;

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


N'oublie pas le point-virgule à la fin de la définition de la classe !


class A;

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

class A
{
...
};


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

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.


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.


1 2 3