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
Laurent Deniau
Jean-Marc Bourguet wrote:
Laurent Deniau writes:
Pendant l'execution du constructeur et du destructeur, l'objet est du
type de la classe construite (et non de la classe la plus derivee).

Appeler un membre virtuel sur this a un comportement defini: la
fonction sera celle de la classe construite ou de l'ancetre la
fournissant (donc c'est un probleme si elle est pure).

Appeler un membre virtuel a travers d'un alias de this qui n'est pas
du type construit ou d'un de ses descendants est indefini.


Ok.

Pour obtenir cet alias, il faut jouer a l'appenti sorcier avec des
reinterpret_cast, d'ou UB. Ni static_cast, ni dynamic_cast ne permettent
d'obtenir cet d'alias derive de this.

a+, ld.

Avatar
Jean-Marc Bourguet
writes:

Alors, avec quoi est-ce que tu proposes de la remplacer,


Les fonctions virtuelles pures sont des fonctions virtuelles comme les
autres sauf qu'on n'est pas oblige de les definir et qu'elles
empechent l'instanciation des classent qui en comportent. C'est un
comportement indefini si elles devraient etre executees mais qu'elles
n'ont pas ete definies. Ca me semble simple, pas de regles
particulieres, pas de changement bizarres dans la recherche quand on
passe de virtuel pur a non pur.

Le probleme qu'elles posent ne me semble pas plus complique pour les
editeurs de liens que celui des inline ou des templates.

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
Jean-Marc Bourguet
Laurent Deniau writes:

Jean-Marc Bourguet wrote:
Laurent Deniau writes:
Pendant l'execution du constructeur et du destructeur, l'objet est du
type de la classe construite (et non de la classe la plus derivee).
Appeler un membre virtuel sur this a un comportement defini: la
fonction sera celle de la classe construite ou de l'ancetre la
fournissant (donc c'est un probleme si elle est pure).
Appeler un membre virtuel a travers d'un alias de this qui n'est pas
du type construit ou d'un de ses descendants est indefini.


Ok.

Pour obtenir cet alias, il faut jouer a l'appenti sorcier avec des
reinterpret_cast, d'ou UB. Ni static_cast, ni dynamic_cast ne permettent
d'obtenir cet d'alias derive de this.


Les alias peuvent provenir de l'appelant. Avec de l'heritage
multiple, ce peut un pointeur sur une base deja contruite passe en
parametre. Pas d'UB ni de jeux dangereux, mais bien qqch qu'on peut
vouloir faire raisonnablement:

struct A {
virtual void f();
};

struct D_A {
virtual void f();
};

struct B {
B(A*p) { p->f(); }
};

struct F: public D_A, B {
F(): B(this) {} // this comme D_A est valide car construit avant
};

Eh non, l'appel virtuel dans B::B() est indefini!

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
K. Ahausse
"Jean-Marc Bourguet" a écrit dans le message de
news:
writes:

Alors, avec quoi est-ce que tu proposes de la remplacer,


Les fonctions virtuelles pures sont des fonctions virtuelles comme les
autres sauf qu'on n'est pas oblige de les definir et qu'elles
empechent l'instanciation des classent qui en comportent. C'est un
comportement indefini si elles devraient etre executees mais qu'elles
n'ont pas ete definies. Ca me semble simple, pas de regles
particulieres, pas de changement bizarres dans la recherche quand on
passe de virtuel pur a non pur.


Cela parait limpide.

Mais, pardonner de revenir sur le sujet, la formulation de Gabriel Dos Reis
: "la fonction est *définie*, mais le code
invoque un fonctionnement indéfini", n'est, alors, pas fondée.


PS : je reviens sur le sujet car je ne prendrais pas à la légère une
réflexion provenant de Gabriel Dos Reis. Même si je pense qu'il s'est
trompé, la valeur du personnage fait que, un doute subsiste.


<CITATION POST PRECEDENT>

#incluse <iostream>
| >

| > struct A {
| > virtual void f() = 0;
| > virtual ~A() { f(); }
| > };
| >
| > void A::f() { std::cout << "coucou !n"; }
|

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).

</CITATION POST PRECEDENT>


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


Jean-Marc Bourguet wrote:

Laurent Deniau writes:
Pendant l'execution du constructeur et du destructeur, l'objet est du
type de la classe construite (et non de la classe la plus derivee).
Appeler un membre virtuel sur this a un comportement defini: la
fonction sera celle de la classe construite ou de l'ancetre la
fournissant (donc c'est un probleme si elle est pure).
Appeler un membre virtuel a travers d'un alias de this qui n'est pas
du type construit ou d'un de ses descendants est indefini.


Ok.

Pour obtenir cet alias, il faut jouer a l'appenti sorcier avec des
reinterpret_cast, d'ou UB. Ni static_cast, ni dynamic_cast ne permettent
d'obtenir cet d'alias derive de this.



Les alias peuvent provenir de l'appelant. Avec de l'heritage
multiple, ce peut un pointeur sur une base deja contruite passe en
parametre. Pas d'UB ni de jeux dangereux, mais bien qqch qu'on peut
vouloir faire raisonnablement:

struct A {
virtual void f();
};

struct D_A {
virtual void f();
};

struct B {
B(A*p) { p->f(); }
};

struct F: public D_A, B {
F(): B(this) {} // this comme D_A est valide car construit avant
};

Eh non, l'appel virtuel dans B::B() est indefini!


Pourquoi? p dans B::B() est de type effectif F* (mais this est de type
B*). p->f() vas donc invoquer legalement D_A::f() sur un this de type
F*. Ou vois-tu un alias de this en tant que B*?

a+, ld.



Avatar
Jean-Marc Bourguet
"K. Ahausse" writes:

"Jean-Marc Bourguet" a écrit dans le message de
news:
writes:

Alors, avec quoi est-ce que tu proposes de la remplacer,


Les fonctions virtuelles pures sont des fonctions virtuelles comme les
autres sauf qu'on n'est pas oblige de les definir et qu'elles
empechent l'instanciation des classent qui en comportent. C'est un
comportement indefini si elles devraient etre executees mais qu'elles
n'ont pas ete definies. Ca me semble simple, pas de regles
particulieres, pas de changement bizarres dans la recherche quand on
passe de virtuel pur a non pur.


Cela parait limpide.


Cette phrase decrit la situation que je voudrais (et qu'en fait je
pensais etre le cas au debut de la discussion), elle ne decrit pas la
situation actuelle.

Mais, pardonner de revenir sur le sujet, la formulation de Gabriel
Dos Reis : "la fonction est *définie*, mais le code invoque un
fonctionnement indéfini", n'est, alors, pas fondée.


Si. Il y a une clause qqpart dans la norme qui dit explicitement que
c'est indefini. Et les 3 compilateurs sur lequel j'ai essaye le code
arretent le programme.

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
Jean-Marc Bourguet
Laurent Deniau writes:

Jean-Marc Bourguet wrote:
Laurent Deniau writes:

Jean-Marc Bourguet wrote:

Laurent Deniau writes:
Pendant l'execution du constructeur et du destructeur, l'objet est du
type de la classe construite (et non de la classe la plus derivee).
Appeler un membre virtuel sur this a un comportement defini: la
fonction sera celle de la classe construite ou de l'ancetre la
fournissant (donc c'est un probleme si elle est pure).
Appeler un membre virtuel a travers d'un alias de this qui n'est pas
du type construit ou d'un de ses descendants est indefini.


Ok.

Pour obtenir cet alias, il faut jouer a l'appenti sorcier avec des
reinterpret_cast, d'ou UB. Ni static_cast, ni dynamic_cast ne permettent
d'obtenir cet d'alias derive de this.
Les alias peuvent provenir de l'appelant. Avec de l'heritage

multiple, ce peut un pointeur sur une base deja contruite passe en
parametre. Pas d'UB ni de jeux dangereux, mais bien qqch qu'on peut
vouloir faire raisonnablement:
struct A {
virtual void f();
};
struct D_A {
virtual void f();
};
struct B {
B(A*p) { p->f(); }
};
struct F: public D_A, B {
F(): B(this) {} // this comme D_A est valide car construit avant
};
Eh non, l'appel virtuel dans B::B() est indefini!


Pourquoi? p dans B::B() est de type effectif F* (mais this est de type
B*). p->f() vas donc invoquer legalement D_A::f() sur un this de type
F*. Ou vois-tu un alias de this en tant que B*?


p et this designent le meme objet (le F en cours de construction)
p n'est pas un B (ou une de ses bases)
c'est donc un comportement indefini.

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
Laurent Deniau
K. Ahausse wrote:
"Jean-Marc Bourguet" a écrit dans le message de
news:

writes:


Alors, avec quoi est-ce que tu proposes de la remplacer,


Les fonctions virtuelles pures sont des fonctions virtuelles comme les
autres sauf qu'on n'est pas oblige de les definir et qu'elles
empechent l'instanciation des classent qui en comportent. C'est un
comportement indefini si elles devraient etre executees mais qu'elles
n'ont pas ete definies. Ca me semble simple, pas de regles
particulieres, pas de changement bizarres dans la recherche quand on
passe de virtuel pur a non pur.



Cela parait limpide.

Mais, pardonner de revenir sur le sujet, la formulation de Gabriel Dos Reis
: "la fonction est *définie*, mais le code
invoque un fonctionnement indéfini", n'est, alors, pas fondée.


Si j'ai bien compris, je suppose qu'il veut dire que la norme ne force
pas le compilateur a faire le lien entre la definition de la methode et
la vtbl de sa classe des lors que celle-ci est declaree virtuelle pure,
et donc la norme invoque le UB a la place (par simplicite?), que la
methode virtuelle soit definie ou non.

Il ajoute que techniquement il n'y a pas d'opposition a eviter l'UB, en
presence ou en l'absence d'une definition de la methode virtuelle - pure
ou non - le mecanisme de lien et la resolution de l'appel etant les
memes. Et d'ajouter que la plupart des compilos ne font pas la
difference dans la pratique et utilisent soit la definition de la
methode s'il est presente, soit la definition d'une autre methode
speciale qui affiche une erreur et fait abort().

PS : je reviens sur le sujet car je ne prendrais pas à la légère une
réflexion provenant de Gabriel Dos Reis.


moi non plus ;-)

Même si je pense qu'il s'est
trompé, la valeur du personnage fait que, un doute subsiste.


pas moi ;-)


<CITATION POST PRECEDENT>

#incluse <iostream>


| >
| > struct A {
| > virtual void f() = 0;
| > virtual ~A() { f(); }
| > };
| >
| > void A::f() { std::cout << "coucou !n"; }
|

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).

</CITATION POST PRECEDENT>


a+, ld.



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


Jean-Marc Bourguet wrote:

Laurent Deniau writes:


Jean-Marc Bourguet wrote:


Laurent Deniau writes:
Pendant l'execution du constructeur et du destructeur, l'objet est du
type de la classe construite (et non de la classe la plus derivee).
Appeler un membre virtuel sur this a un comportement defini: la
fonction sera celle de la classe construite ou de l'ancetre la
fournissant (donc c'est un probleme si elle est pure).
Appeler un membre virtuel a travers d'un alias de this qui n'est pas
du type construit ou d'un de ses descendants est indefini.


Ok.

Pour obtenir cet alias, il faut jouer a l'appenti sorcier avec des
reinterpret_cast, d'ou UB. Ni static_cast, ni dynamic_cast ne permettent
d'obtenir cet d'alias derive de this.


Les alias peuvent provenir de l'appelant. Avec de l'heritage
multiple, ce peut un pointeur sur une base deja contruite passe en
parametre. Pas d'UB ni de jeux dangereux, mais bien qqch qu'on peut
vouloir faire raisonnablement:
struct A {
virtual void f();
};
struct D_A {
virtual void f();
};
struct B {
B(A*p) { p->f(); }
};
struct F: public D_A, B {
F(): B(this) {} // this comme D_A est valide car construit avant
};
Eh non, l'appel virtuel dans B::B() est indefini!


Pourquoi? p dans B::B() est de type effectif F* (mais this est de type
B*). p->f() vas donc invoquer legalement D_A::f() sur un this de type
F*. Ou vois-tu un alias de this en tant que B*?



p et this designent le meme objet (le F en cours de construction)
p n'est pas un B (ou une de ses bases)
c'est donc un comportement indefini.


arf, je me suis fais avoir, je cherchais dans B ;-)

c'est dans F::F() que tu crees l'alias en appellant B::B() avec deux
this avec deux types statiques differents mais le meme type effectif.
Donc p->f() invoque un B::f() qui n'exite pas. Bien vu, ceci dit tu n'as
pas besoin de l'heritage multiple pour avoir le UB:

struct A {
A(A*p) { p->f(); } // invoque A::f(p), Ok mais pas voulu
virtual void f();
};

struct D : A {
D(): A(this) {} // alias
virtual void f();
};

Ce code marchera (contraitement a ton exemple qui met bien les point sur
les 'i' ;-), mais je pense que la norme lui attribue quand meme un UB, p
etant un alias sur D* mais de type effectif A* au meme titre que this...

a+, ld.





Avatar
Jean-Marc Bourguet
Laurent Deniau writes:

c'est dans F::F() que tu crees l'alias en appellant B::B() avec deux
this avec deux types statiques differents mais le meme type
effectif. Donc p->f() invoque un B::f() qui n'exite pas. Bien vu,
ceci dit tu n'as pas besoin de l'heritage multiple pour avoir le UB:

struct A {
A(A*p) { p->f(); } // invoque A::f(p), Ok mais pas voulu
virtual void f();
};

struct D : A {
D(): A(this) {} // alias
virtual void f();
};

Ce code marchera (contraitement a ton exemple qui met bien les point
sur les 'i' ;-), mais je pense que la norme lui attribue quand meme
un UB, p etant un alias sur D* mais de type effectif A* au meme
titre que this...


Je crois que c'est bien defini. C'est la partie que j'ai coupe de
l'exemple de la norme : le p dans A::A designe le sous-objet en cours
de construction ou un de ses ancetres (l'appel est donc a A::f). Note
qu'avec l'heritage multiple, on doit pouvoir avoir le pb meme si le
type statique est bon mais que le sous-objet designe n'est pas celui
en cours de construction/destruction.

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