OVH Cloud OVH Cloud

Forme canonique de Coplien et poymorphisme

137 réponses
Avatar
Y a n n R e n a r d
Bonjour à tous,

tout d'abord, merci à tous pour ce newsgroup très instructif, c'est la
première fois que je poste, alors je vais essayer de ne pas me faire
incendier par Fabien ;) :p

Voila mon problème : j'ai un client qui souhaite que toutes les classes
définies dans le projet sur lequel je travaille soient sous la forme
canonique de Coplien (pour ceux qui ne connaissent pas : constructeur
par défaut, constructeur par copie, operateur d'affectation et
destructeur virtuel ou non). Pour que ce soit plus clean (selon eux), le
client demande que toutes les classes dérivent d'une classe de base...
Sauf que je ne vois pas du tout l'intéret de faire ca car je ne vois pas
en quoi ca force à respecter la forme canonique de Coplien.

Concernant l'operateur d'affectation, si je le met virtuel (est ce
quelque chose de propre ?!) comment puis je faire en sorte que les
classes filles utilisent un opérateur correct ? cast dynamique ?

Concernant l'operateur de recopie, là, je vois pas du tout du tout du
tout :)

Bref, je comprends pas du tout cette demande, si quelqu'un pense que
c'est raisonable ou explicable, je veux bien une précision sur l'utilité...

Pour terminer, il faut aussi déporter le code du constructeur et du
destructeur dans une fonction séparée, et là, je comprends plus du
tout... genre quelque chose comme ca :

T::T(void)
{
construct();
}

T::~T(void)
{
destruct();
}

void T::construct(void)
{
// ...
}

void T::destruct(void)
{
// ...
}

Voila voila, votre avis est le bienvenu :)
Merci d'avance,

Yann Renard

10 réponses

Avatar
kanze
Gabriel Dos Reis wrote in message
news:...
Jean-Marc Bourguet writes:


[...]
| pour les 3 compilateurs. Je suis un peu confu de n'avoir pas essayé
| plus tot. Mais bon, j'ai (ré?)appris une particularité
| supplémentaire des virtuelles pures.

Oui, mais le fond demeure que tu as a un point : s'ils mettent une
adresse spéciale, ils pourraient en profiter pour mettre une adresse
utile, cela de la fonction virtuelle pure. Après tout, être une classe
abstraite veut juste dire qu'on ne peut pas créer un objet complet de
cette classe -- pas qu'il faille tout une armada spéciale.


Le problème, c'est qu'on travaille toujours avec une idée précise de ce
que c'est la compilation séparée (sauf peut-être pour certains aspects
des templates). Donc, lors que le compilateur génère le vtbl, il ne sait
pas si la fonction pûre est définie ou non. (Pour les virtuelles
non-pûre, c'est un comportement indéfini si elle n'est pas définie.)
S'il met l'adresse de la fonction, et qu'elle n'est pas définie, il y
aurait une erreur lors de l'édition de liens.

Ceci dit, ça ne justifie pas un comportement indéfini. Mais le
comportement défini doit être quelque chose du genre : appelle
terminate(), ou « outputs an implementation defined message to cout,
then calls abort() ».

--
James Kanze GABI Software http://www.gabi-soft.fr
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
kanze
Jean-Marc Bourguet wrote in message
news:...
writes:

Jean-Marc Bourguet wrote in message

[les membres virtuels purs] sont des membres virtuels comme les
autres a deux differences pres:
- les descendants instanciables sont obliges de les supplanter
- on peut ne pas les definir (a l'exception du destructeur)


D'où est-ce que tu tiens ça ?


J'ai l'impression que tu confonds "on peut ne pas" avec "on ne peut
pas".


Oui. (Et j'avoue que ça m'avait étonné de toi.)

En fait, à peu près la seule véritable différence entre une fonction
virtuelle pure et une virtuelle tout courte, c'est que la résolution
*dynamique* ne doit jamais rétrouver une fonction virtuelle pure ;


class A {
~A() { g(); }
virtual void f() = 0;
void g();
};

void A::g() {
f();
}
void A::f() {
std::cout << "In A::f(), a pure virtual functionn";
}

Ou bien tu ne consideres pas l'appel de f dans g comme etant une
resolution dynamique?


C'est une resolution dynamique. Et puisqu'elle trouverait logiquement
A::f() quand on appelle g() du constructeur, le programme a un
comportement indéfini.

Que veux-tu d'autre ? Dans la pratique, quand le compilateur génère le
vtbl, il ne peut pas savoir si A::f() est définie ou non. Or, s'il en
met l'adresse, et qu'elle n'est pas définie, il y aura une erreur à
l'édition de liens. Même sans l'appel de g() dans le constructeur, or
qu'alors, le programme serait parfaitement légal. Donc, en tout état de
cause, il ne peut pas mettre l'adresse de A::f() dans le vtbl -- il faut
mettre quelque chose d'autre. Je crois que certains compilateurs anciens
mettaient simplement NULL ; tous les compilateurs que je connais
aujourd'hui met l'adresse d'une fonction qui finit par appeler abort(),
ce qui me semble la meilleur solution.

--
James Kanze GABI Software http://www.gabi-soft.fr
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
Gabriel Dos Reis
writes:

| Gabriel Dos Reis wrote in message
| news:...
| > Jean-Marc Bourguet writes:
|
| [...]
| > | pour les 3 compilateurs. Je suis un peu confu de n'avoir pas essayé
| > | plus tot. Mais bon, j'ai (ré?)appris une particularité
| > | supplémentaire des virtuelles pures.
|
| > Oui, mais le fond demeure que tu as a un point : s'ils mettent une
| > adresse spéciale, ils pourraient en profiter pour mettre une adresse
| > utile, cela de la fonction virtuelle pure. Après tout, être une classe
| > abstraite veut juste dire qu'on ne peut pas créer un objet complet de
| > cette classe -- pas qu'il faille tout une armada spéciale.
|
| Le problème, c'est qu'on travaille toujours avec une idée précise de ce
| que c'est la compilation séparée (sauf peut-être pour certains aspects
| des templates). Donc, lors que le compilateur génère le vtbl, il ne sait

La compilation séparée a peu de chose à avoir dans cette histoire --
le fond du problème lorsque tu l'analyses bien est que le même que
pour « ::operator new ».

| pas si la fonction pûre est définie ou non. (Pour les virtuelles
| non-pûre, c'est un comportement indéfini si elle n'est pas définie.)
| S'il met l'adresse de la fonction, et qu'elle n'est pas définie, il y
| aurait une erreur lors de l'édition de liens.

Cela n'a aucune expèce d'importance.

La technologie nécessaire pour faire marcher ce dont Jean-Marc et moi
proposons/discutons est déjà requise implémenter le remplacement de
« operator new » et consort : si l'utilisateur fournit une définition
quelque part dans le programme alors cette définition prend précédence
sur celle de l'implémentation. C'est exactement la même chose ici : si
la fonction virtuelle pure est définie quelque part alors cette
définition prend précédence sur celle donnée par l'implémentation (qui
usuellement est avoter le programme avec un message d'erreur disant
qu'une fonction virtuelle pure a été appelée).

| Ceci dit, ça ne justifie pas un comportement indéfini. Mais le
| comportement défini doit être quelque chose du genre : appelle
| terminate(), ou « outputs an implementation defined message to cout,
| then calls abort() ».

Si on doit faire ça, autant faire quelque chose d'utile -- parce que
l'effort est le même.

-- Gaby
Avatar
Gabriel Dos Reis
writes:

| Jean-Marc Bourguet wrote in message
| news:...
| > Gabriel Dos Reis writes:
|
| > > | > #include <iostream>
|
| > > | > struct A {
| > > | > virtual void f() = 0;
| > > | > virtual ~A() { f(); }
| > > | > };
|
| > > | > void A::f() { std::cout << "coucou !n"; }
|
| > > | Oui, je vois, tu as raison aussi :-)
|
| > > Non. Je n'ai pas raison : la fonction est *définie*, mais le code
| > > invoque un fonctionnement indéfini (la plupart des compilateurs
| > > l'accepteront sur la base du méchanisme d'appel).
|
| > Evidemment ils ont ete mettre en 10.4/3 et une clause qui contredit ce
| > qu'on peut penser en ne regardant que 12.7 (et en particulier 12.7/3).
|
| > Je comprends que l'effet soint indefini quand la fonction n'est pas
| > definie, mais si elle l'est ca me choque un peu.
|
| Je ne sais pas. C'est le résultat d'un ensemble de règles, chacune
| simple et logique en soi :
| - la résolution dynamique ne trouve jamais une fonction virtuelle
| pûre,

Cette règle ne me semble ni simple ni logique en soi.

-- Gaby
Avatar
Gabriel Dos Reis
writes:

| Que veux-tu d'autre ?

Quelque chose d'utile.

| Dans la pratique, quand le compilateur génère le
| vtbl, il ne peut pas savoir si A::f() est définie ou non.

Il se débrouille bien avec « operator new », il peut aussi se
débrouiller pour faire quelque d'utile, qui nécessite juste le mÊme
effort.

-- Gaby
Avatar
kanze
Gabriel Dos Reis wrote in message
news:...
writes:

| Gabriel Dos Reis wrote in message
| news:...
| > Jean-Marc Bourguet writes:

| [...]
| > | pour les 3 compilateurs. Je suis un peu confu de n'avoir pas
| > | essayé plus tot. Mais bon, j'ai (ré?)appris une particularité
| > | supplémentaire des virtuelles pures.

| > Oui, mais le fond demeure que tu as a un point : s'ils mettent une
| > adresse spéciale, ils pourraient en profiter pour mettre une
| > adresse utile, cela de la fonction virtuelle pure. Après tout,
| > être une classe abstraite veut juste dire qu'on ne peut pas créer
| > un objet complet de cette classe -- pas qu'il faille tout une
| > armada spéciale.

| Le problème, c'est qu'on travaille toujours avec une idée précise de
| ce que c'est la compilation séparée (sauf peut-être pour certains
| aspects des templates). Donc, lors que le compilateur génère le
| vtbl, il ne sait

La compilation séparée a peu de chose à avoir dans cette histoire --
le fond du problème lorsque tu l'analyses bien est que le même que
pour « ::operator new ».


À la différence près que le nom de « ::operator new » est connu de
l'implémentation.

| pas si la fonction pûre est définie ou non. (Pour les virtuelles
| non-pûre, c'est un comportement indéfini si elle n'est pas définie.)
| S'il met l'adresse de la fonction, et qu'elle n'est pas définie, il
| y aurait une erreur lors de l'édition de liens.

Cela n'a aucune expèce d'importance.

La technologie nécessaire pour faire marcher ce dont Jean-Marc et moi
proposons/discutons est déjà requise implémenter le remplacement de
« operator new » et consort : si l'utilisateur fournit une définition
quelque part dans le programme alors cette définition prend précédence
sur celle de l'implémentation. C'est exactement la même chose ici : si
la fonction virtuelle pure est définie quelque part alors cette
définition prend précédence sur celle donnée par l'implémentation (qui
usuellement est avoter le programme avec un message d'erreur disant
qu'une fonction virtuelle pure a été appelée).


Dans le cas de l'operator new, l'implémentation que j'ai toujours vu,
c'est que la bibliothèque du compilateur en fournit une implémentation,
et que cette bibliothèque est ajoutée à la fin. Donc, si l'externe a
déjà été résolu, on ne prend pas la module de la bibliothèque du
compilateur.

Alors, comment ça marche-t-il avec une fonction virtuelle pure ? La
bibliothèque du compilateur a une implémentation pour tous les noms
possibles ? À mon avis, il faudrait au moins une espèce de «@wildcard@»
dans le nom de la fonction dans la bibliothèque du compilateur, ce que
ne supporte pas la plupart des éditeurs de liens. Ou la possibilité de
lui donner deux noms, la forme décorée du nom de la fonction réele, et
le nom de la fonction de ratrappage dans la bibliothèque du compilateur.

| Ceci dit, ça ne justifie pas un comportement indéfini. Mais le
| comportement défini doit être quelque chose du genre : appelle
| terminate(), ou « outputs an implementation defined message to cout,
| then calls abort() ».

Si on doit faire ça, autant faire quelque chose d'utile -- parce que
l'effort est le même.


À vrai dire, je trouve que le comportement ci-dessus est utile aussi.
Après tout, si la fonction n'est pas définie, tu ne peux guère faire
mieux. Et je n'aime pas les exceptions, où le comportement dépend de ce
qui pourrait être défini ou non dans une module qu'on ne connaît pas. Je
n'aime pas le comportement indéfini, mais je trouve que dans les faits,
le comportement réel des compilateurs actuels me convient.

--
James Kanze GABI Software http://www.gabi-soft.fr
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
kanze
Gabriel Dos Reis wrote in message
news:...
writes:

| Jean-Marc Bourguet wrote in message
| news:...
| > Gabriel Dos Reis writes:

| > > | > #include <iostream>

| > > | > struct A {
| > > | > virtual void f() = 0;
| > > | > virtual ~A() { f(); }
| > > | > };

| > > | > void A::f() { std::cout << "coucou !n"; }

| > > | Oui, je vois, tu as raison aussi :-)

| > > Non. Je n'ai pas raison : la fonction est *définie*, mais le
| > > code invoque un fonctionnement indéfini (la plupart des
| > > compilateurs l'accepteront sur la base du méchanisme d'appel).

| > Evidemment ils ont ete mettre en 10.4/3 et une clause qui
| > contredit ce qu'on peut penser en ne regardant que 12.7 (et en
| > particulier 12.7/3).

| > Je comprends que l'effet soint indefini quand la fonction n'est
| > pas definie, mais si elle l'est ca me choque un peu.

| Je ne sais pas. C'est le résultat d'un ensemble de règles, chacune
| simple et logique en soi :
| - la résolution dynamique ne trouve jamais une fonction virtuelle
| pûre,

Cette règle ne me semble ni simple ni logique en soi.


Alors, avec quoi est-ce que tu proposes de la remplacer, étant donné
qu'on n'est pas obligé à fournir une définition pour une fonction
virtuelle pure. Qu'on soit obligé à fournir une définition ? Ou que la
résolution dynamique trouve peut-être une fonction virtuelle pûre, et
peut-être pas, selon ce qui se trouve dans d'autres unités de
compilation, que tu ne connais pas ?

--
James Kanze GABI Software http://www.gabi-soft.fr
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
kanze
Gabriel Dos Reis wrote in message
news:...

writes:

| Que veux-tu d'autre ?

Quelque chose d'utile.

| Dans la pratique, quand le compilateur génère le
| vtbl, il ne peut pas savoir si A::f() est définie ou non.

Il se débrouille bien avec « operator new », il peut aussi se
débrouiller pour faire quelque d'utile, qui nécessite juste le mÊme
effort.


Ce n'est pas le compilateur qui se débrouille avec operator new. C'est
l'éditeur de liens. Et il le fait sans règles spéciales, simplement
parce qu'il existe une fonction de ce nom dans la bibliothèque du
compilateur, que l'éditeur de liens régarde en dernier. Pour que ça
marche ici, il faudrait que la bibliothèque du compilateur ait aussi une
fonction nommée A::f().

--
James Kanze GABI Software http://www.gabi-soft.fr
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
Laurent Deniau
wrote:
Gabriel Dos Reis wrote in message
news:...

writes:



| Jean-Marc Bourguet wrote in message
| news:...
| > Gabriel Dos Reis writes:



| > > | > #include <iostream>



| > > | > struct A {
| > > | > virtual void f() = 0;
| > > | > virtual ~A() { f(); }
| > > | > };



| > > | > void A::f() { std::cout << "coucou !n"; }



| > > | Oui, je vois, tu as raison aussi :-)



| > > Non. Je n'ai pas raison : la fonction est *définie*, mais le
| > > code invoque un fonctionnement indéfini (la plupart des
| > > compilateurs l'accepteront sur la base du méchanisme d'appel).



| > Evidemment ils ont ete mettre en 10.4/3 et une clause qui
| > contredit ce qu'on peut penser en ne regardant que 12.7 (et en
| > particulier 12.7/3).



| > Je comprends que l'effet soint indefini quand la fonction n'est
| > pas definie, mais si elle l'est ca me choque un peu.



| Je ne sais pas. C'est le résultat d'un ensemble de règles, chacune
| simple et logique en soi :
| - la résolution dynamique ne trouve jamais une fonction virtuelle
| pûre,



Cette règle ne me semble ni simple ni logique en soi.



Alors, avec quoi est-ce que tu proposes de la remplacer, étant donné
qu'on n'est pas obligé à fournir une définition pour une fonction
virtuelle pure. Qu'on soit obligé à fournir une définition ? Ou que la
résolution dynamique trouve peut-être une fonction virtuelle pûre, et
peut-être pas, selon ce qui se trouve dans d'autres unités de
compilation, que tu ne connais pas ?


Sur ce point, je suis d'accord avec Gaby. Pourquoi la deuxieme solution
n'est pas viable puisque dans la pratique c'est ce qui se passe? En quoi
est ce que la connaissance des autres unites de compilation est
importante du moment que l'on sait ou est la vtbl et que toute
definition s'enregistre dans la vtbl, meme si au depart elle etait
declaree virtuelle pure.

a+, ld.


Avatar
Laurent Deniau
Laurent Deniau wrote:
Jean-Marc Bourguet wrote:

Laurent Deniau writes:

Donc B* devient un A* et reste un A*.
[...]




delete a;

peut donc être compilé en qqch du genre (j'ai jamais
travaillé sur un compilateur C++, il y a peut-être des
choses que j'oublie):

__base = a->__get_most_derived_object();
__sz = a->__get_size_of_most_derived_object();
__delete = a->__get_delete_operator();
a->__destructor_in_charge_of_virtual_members(__base);
(*__delete)(__base, __sz);



Je pense que tu as raison. Je viens de faire un test (code a la fin) qui
montre que les deux sont compatibles: transition du type dans les
ctor/dtor + delete/RTTI virtuels (j'aurais du commencer par la). Par
contre aux vues de ce que Gaby a explique, je ne vois pas comment le
compilo peut s'en sortir sans reaffecter le __vtbl de l'objet lorsqu'il
mute l'objet entre deux dtors. Si en entrant dans A::~A(), this qui
etait un B* devient vraiment un A*, __vtbl doit necessairement changer.
Si ce n'est pas ce qu'il fait, j'aimerais bien que qqun m'explique
comment le compilo s'en sort avec l'appel de f2() dans A::f1()?


Pour lever cette question, il est facile de rajouter deux lignes dans le
code que j'ai propose. Effectivement, le compilateur change les __vtbl
des objets pendant la transition des ctor/dtor (en tout cas gcc 3.3.4 le
fait). Je comprends mieux pourquoi cote assembleur le compilo utilise
des 'frames' plutot qu'une bete copie d'une instance statique deja
initialise a vide mais avec les bons __vtbl (ce que fait oopc). Dans le
cas de l'heritage multiple, chaque transition de ctor/dtor doit rajouter
un coup non negligeable au runtime.

Donc quand Gaby dit que un B* devient un A* en entrant dans A::~A(), il
a complement raison. Ce qui veut dire ensuite que la resolution des
methodes (virtuelle ou non) reste inchange: pas de semantique
particuliere dans les ctor/dtor.

Et donc comme dit Gaby, la regle de James est compliquee voir illogique
car cela n'a rien a voir avec la semantique de resolution des appels
virtuels dans les ctor/dtor. Seulement avec la transition du type de
this. Du moins c'est ma conclusion pour l'instant. Des reclamations ? ;-)

a+, ld.