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

7 réponses

Avatar
Gabriel Dos Reis
writes:

| Falk Tannhäuser wrote in message
| news:<41aa38ec$0$25533$...
| > Gabriel Dos Reis wrote:
| > > Falk Tannhäuser writes:
|
| > > | alors que pB1 pointe bien sur le sous-objet de classe
| > > | 'B' dans l'objet 'd' de classe 'D' ; ledit sous-objet
| > > | a été construit par le constructeur 'B::B()' ; ses
| > > | types statique et dynamique ont été 'B' dans ledit
|
| > > Et ils le restent. Tu confonds une chose ici : le type dynamique de
| > > l'expression « *pB1 » (qui évolue avec la completion de la
| > > constrution de l'objet "D" avec le type de l'objet "B")
|
| > > | constructeur, mais son type dynamique devient 'D'
|
| > > Non, pas du tout. Son type dynamique ne change pas. La différence
| > > que tu vois est dûe au fait que l'objet complet ou l'objet le plus
| > > dérivé qui contient ce sous-objet est construit quand tu invoques
| > > foo() dans main() ; et donc la résolution de l'appel à la fonction
| > > virtuelle va se faire selon cet objet complet. Mais le sous objet
| > > "B" ne change pas de type dynamique. Heureusement, sinon
| > > dynamic_cast<> n'a plus de sens !
|
| > Pour résumer :
| >
| > On est tous les deux d'accord que :
| > - pB1 pointe sur le sous-objet "B" dans l'objet "D",
| > - le type dynamique de *pB1 est "D" pendant la durée de vie de l'objet
| > complet "D", ainsi que dans le constructeur et le destructeur de D
| > (et "B" dans le constructeur et le destructeur de "B").
|
| > Notre désaccord porte sur le type dynamique du sous-objet "B" pendant
| > la durée de vie de l'objet complet "D" (ainsi que dans le constructeur
| > / destructeur de "D") :
|
| Je crois que c'est là la clé de la discussion. Si on considère pB comme
| un pointeur au sous-objet, le sous-objet à bien le type dynamique B,
| toujours. En revanche, ce sous-objet fait partie d'un objet complet,
| dont le type dynmaique varie lors de la construction et de la
| destruction.

L'objet complet est sans aucun doute, l'objet "D".

| Dans la pratique, en tant qu'utilisateur, j'utilise pB pour accéder à
| l'objet, non pour accéder au sous-objet. Donc, je prends en compte le
| type dynamique de l'objet complet.

Bah non, puisque tu fais gaffe à ce que tu fais dans le constructeur ;-)

|
| Dans la norme, cette distinction pourrait avoir de l'importance.
| Considérons le suivant :
|
| struct B1 { B1() ; virtual void f() ; } ;
| struct B2 { B2() ; virtual void f() ; } ;
| struct D : B1, B2 {} ;
|
| B1 * pB1 ;
|
| B1::B1()
| {
| pB1 = this ;
| } ;
|
| B2::B2()
| {
| pB1->f() ;
| }
|
| Est-ce que l'appel pB1->f() est légal ?

Cet appel n'est pas valide.

| Si on considère que pB1 point à
| l'objet complet en cour de construction, alors, le type dynamique de
| l'objet complet est bien B2, et l'appel à travers un B1* est illegal. Si

L'appel est invalide, mais pas pour la raison quelle que tu la
formules.

12.7/3

Member functions, including virtual functions (10.3), can be called
during construction or destruction (12.6.2). When a virtual function
is called directly or indirectly from a constructor (including from
the mem-initializer for a data member) or from a destructor, and the
object to which the call applies is the object under construction or
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
destruction, the function called is the one defined in the
constructor or destructor's own class or in one of its bases, but
not a function overriding it in a class derived from the constructor
or destructor's class, or overriding it in one of the other base
classes of the most derived object (1.8). If the virtual function
call uses an explicit class member access (5.2.5) and the
object-expression refers to the object under construction or
destruction but its type is neither the constructor or destructor's
own class or one of its bases, the result of the call is undefined.


Note bien que la norme dit « the object to which the call applies ».
La norme implique bien une distinction entre *différents objets* et non
un même objet avec différents types.

| on considère que pB1 point simplement au sous-objet, ce sous-objet est
| complètement construit, et a un type B1, même s'il ne fait pas encore
| partie de l'objet complete.

Mais l'histoire ne se termine pas là. pB1 point au sous-objet "B1" et
cet sous-objet est complètement construit. Cependant parce que l'appel
est un appel à une fonction virtuelle, sa résolution (dynamique) doit
se faire en tenant compte du type de l'objet complet qui contient "B".
Résolution d'appel de fonction dynamique 101.
Cet objet complet est en cours de construction, mais la classe du
constructeur en cours d'exécution n'est pas B1 ni une classe dérivée
(directe ou indirecte) de B1. Donc le fonctionnement est
indéfini. C'est ce que dit 12.7/3.

| Si on interprète « object » dans §3.8 de manière à inclure des
| sous-objets (ce qui me semble *une* interprétation raisonable, mais pas
| la seule interprétation raisonable),

Le terme « objet » peut désigner aussi « sous-objet » ; ce n'est pas
seulement une interprétation raisonable, mais c'est aussi le sens
utilisé pour déinir « sous-objet » et 12.7/3 en question :-)

| la réponse serait bien oui. En

Non.

| revanche, §12.7/3 semble dire clairement que non, et contient un exemple
| à peu près pareil qui dit que c'est un comportement indéfini. À mon
| avis, le texte dans §12.7 fait penché la balance en faveur de
| l'interprétation que « object » dans §3.8 signifie l'objet complet (du
| type le plus dérivé), et exclut les sous-objets.

Non. Voir ci-haut.

| Dans l'ensemble, j'aurais préféré l'autre interprétation. Dans
| l'ensemble, je ne vois pas de problèmes, au niveau de l'implémentation,
| à dire que l'utilisation des sous-objets dont on est déjà sorti du
| constructeur est valide. Mais il y a probablement des problèmes que j'ai
| raté.

Voir ci-haut.

J'avais choqué sur l'utilisation « type effectif » faite par Laurent
-- « type effectif » est un terme C, et non C++, pour désigner quelque
chose de légèrement différent -- mais au fond, ce n'est pas si mal.
Si on sait de quoi on parle :-)

-- Gaby
Avatar
Gabriel Dos Reis
writes:

| Gabriel Dos Reis wrote in message
| news:...
| > drkm writes:
|
| > | Arnaud Meurgues writes:
|
| > | > Gabriel Dos Reis wrote:
|
| > | >> Un constructeur d'une classe de base construit le sous-objet de
| > | >> la classe de base, et non l'objet complet le plus dérivé. Et le
| > | >> type dynamique de ce sous-objet reste, tout au long de sa vie, le
| > | >> type statique vu par le constructeur. Il n'y a jamais de
| > | >> changement
|
| > | > Toute cette discussion me fait me poser une question.
|
| > | > Quel est le sens de "type statique" d'un objet ou de "type
| > | > dynamique" d'un objet ?
|
| > | > Un objet a toujours le même type. En revanche, on peut parler du
| > | > type statique d'une variable et du type dynamique d'une variable.
|
| > | Si je me souviens bien, la définition de type dynamique (1.3.8, je
| > | pense) se réfère au type le plus dérivé d'une *expression* lvalue.
| > | Dans le cas d'une rvalue, elle définit le type dynamique comme égal
| > | au type statique. Remarque la différence entre « this » et « * this
| > | ».
|
| > Exact.
|
| C'est §1.3.3, non §1.3.8.

Je n'avais pas vérifié le numéro.

| Et c'est plus compliqué que ça, parce que la
| «@définnition » est complétée par d'autres clauses de la norme.

Si tu trouves que c'est compliqué, je ne vais pas systématiquement te
contredire ; pour moi, c'est simple :-)

| Ou plus exactement, la définition ici ne prend en compte que les objets
| «@vivants », c-à-d dont la durée de vie (§3.8) a commencée. Or, la durée
| de vie ne commence qu'en sortie du constructeur (selon §3.8). Par
| ailleurs, §12.6 et §12.7 explique le comportement des objets pendant les
| constructeurs et les destructeurs, y compris le comportement des
| expressions lvalue qui désignent de tels objets. Formellement, je ne
| sais pas si on pourrait parler du « type dynamique de l'objet » dans ces
| cas-là, étant donné que formalement, il n'y a pas encore d'objet. Mais
| pratiquement, le *comportement* est celui d'un objet dont le type est
| celui du constructeur ou du destructeur en cours.

C'est pour cela qu'il faut revenir aux principes premiers -- comme
dirait le Père :-)

Ici, le principe premier est qu'à chaque étape de la construction d'un
objet (étape bien défini), il y a le type le plus dérivé dont le
constructeur ou destructeur en cours d'exécution détermine le
fonctionnement dynamique (ou type dynamique).

| Et pratiquement, on ne
| peut pas négligé non plus l'objet complet (qui n'est pas encore
| construit), parce qu'on peut avoir des expressions lvalue qui sont
| valide, et désigne un objet de type dynamique B, qui cesse d'être valide
| sans que la durée de vie du sous-objet qu'il désigne cesse, puis
| redevient valide, avec un type dynamique différent -- dans tout ça,
| l'expression lvalue n'a cessé de désigner l'objet complet de type
| dérivé.

Il y a des expression invalide et des expressions valides, ça c'est
pas nouveau.

-- Gaby
Avatar
Gabriel Dos Reis
writes:

| Est-ce que mes propos auraient été plus clair si javais dit que c'est le
| type dynamique du non objet qui change ?
^^^^^^^^^

Qu'est-ce ?

| En fait, je résonne en termes de l'OO, c-à-d le comportement d'un objet.
| En C++, l'objet est désigné par une expression lvalue. Et le
| comportement change sans qu'on modifie l'expression lvalue pour qu'il
| désigne un autre objet.

Là, je comprends -- enfin un peu -- ce que tu dis :-)

-- Gaby
Avatar
drkm
writes:

C'est §1.3.3, non §1.3.8.


Désolé. J'aurais dû vérifier. Mais j'ai prévenu que c'était de
mémoire. Et il faut se méfier de ma mémoire. Ça je l'avais pas
dit ;-).

--drkm

Avatar
drkm
Laurent Deniau writes:

drkm wrote:

Mais si je comprend bien, il faut lire le schéma comme « Les types
"object" et "class" pointent chacun vers un objet de type "vtbl",
éventuellement différent.


euh non. Il pointe vers la meme vtbl.


Oui. Je pense que je me suis embrouillé avec la conclusion que
j'avais en tête, de pouvoir utiliser le pointeur sur la vtbl d'une
classe pour obtenir une résolution statique.

[...]

Avec,
j'imagine, dans ce dernier cas, l'absence de transtypage implicite en
OOPC vers une classe de base, et donc de calcul de déplacement, cela
devant être demandé explicitement par l'utilisateur.


oui et non.

si tu veux un transtypage efficace, tu dois le demander a l'utilisateur.
sinon il peut etre dynamique mais c'est alors aussi 'lent' qu'un
dynamic_cast. Pour le down-cast c'est pas tres utile, mais pour le
up-cast c'est indispensable.


Je ne suis pas sûr de comprendre. L'exemple que j'avais en tête
était quelque chose comme :

void f( Base * b ) {
}
void g() {
Derived * d = ... ;
f( d ) ;
}

Cela est légal en C++, et le compilo s'occupe du calcul de
l'éventuel déplacement. Je suppose que l'on ne saurait obtenir la
même utilisation avec OOC sans utiliser de « typedef void * Base ».
Si ?

--drkm


Avatar
kanze
Gabriel Dos Reis wrote in message
news:...

[...]

J'avais choqué sur l'utilisation « type effectif » faite par Laurent
-- « type effectif » est un terme C, et non C++, pour désigner quelque
chose de légèrement différent -- mais au fond, ce n'est pas si mal.
Si on sait de quoi on parle :-)


Tout à fait. L'expression convient bien -- dommage que c'est déjà prise.

Ce qui m'avait choqué, c'est l'idée que le pointeur change de type --
les pointeurs ne changent pas de type. Quant aux objets : je crois que
notre différence est plutôt sur l'aspect lexique. Le comportement de
l'objet change. Qu'on veut expliquer ce changement comme un changement
de type ou non. Si on se base sur la norme, la solution adoptée, c'est
que l'objet n'existe pas encore (sa durée de vie ne commence qu'en
sortie du constructeur de l'objet complet) -- en suite, on définit un
certain nombre de choses qu'on peut faire avec l'objet qui n'existe pas
encore. De ce point de vue, évidemment, il n'y a pas de changement de
type de l'objet, parce qu'il n'y a pas encore d'objet. Même si pour
certaines choses, il se comporte comme un objet.

--
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:

| Gabriel Dos Reis wrote in message
| news:...
| > drkm writes:

| > | Arnaud Meurgues writes:

| > | > Gabriel Dos Reis wrote:

| > | >> Un constructeur d'une classe de base construit le sous-objet
| > | >> de la classe de base, et non l'objet complet le plus
| > | >> dérivé. Et le type dynamique de ce sous-objet reste, tout au
| > | >> long de sa vie, le type statique vu par le constructeur. Il
| > | >> n'y a jamais de changement

| > | > Toute cette discussion me fait me poser une question.

| > | > Quel est le sens de "type statique" d'un objet ou de "type
| > | > dynamique" d'un objet ?

| > | > Un objet a toujours le même type. En revanche, on peut parler du
| > | > type statique d'une variable et du type dynamique d'une variable.

| > | Si je me souviens bien, la définition de type dynamique
| > | (1.3.8, je pense) se réfère au type le plus dérivé d'une
| > | *expression* lvalue. Dans le cas d'une rvalue, elle définit le
| > | type dynamique comme égal au type statique. Remarque la
| > | différence entre « this » et « * this ».

| > Exact.

| C'est §1.3.3, non §1.3.8.

Je n'avais pas vérifié le numéro.


J'avais juste ajouté cette information, au cas où quelqu'un d'autre
voulait régarder aussi.

| Et c'est plus compliqué que ça, parce que la « définnition » est
| complétée par d'autres clauses de la norme.

Si tu trouves que c'est compliqué, je ne vais pas systématiquement te
contredire ; pour moi, c'est simple :-)


Par rapport à la résolution des surcharges ou l'instantiation des
templates, c'est simple. C'est juste qu'il y a plus derrière que la
phrase simple dans §1.3.3 laisse voir. Comme j'ai dit, l'information est
complétée ailleurs.

| Ou plus exactement, la définition ici ne prend en compte que les
| objets « vivants », c-à-d dont la durée de vie (§3.8) a
| commencée. Or, la durée de vie ne commence qu'en sortie du
| constructeur (selon §3.8). Par ailleurs, §12.6 et §12.7 explique le
| comportement des objets pendant les constructeurs et les
| destructeurs, y compris le comportement des expressions lvalue qui
| désignent de tels objets. Formellement, je ne sais pas si on
| pourrait parler du « type dynamique de l'objet » dans ces cas-là,
| étant donné que formalement, il n'y a pas encore d'objet. Mais
| pratiquement, le *comportement* est celui d'un objet dont le type
| est celui du constructeur ou du destructeur en cours.

C'est pour cela qu'il faut revenir aux principes premiers -- comme
dirait le Père :-)

Ici, le principe premier est qu'à chaque étape de la construction d'un
objet (étape bien défini), il y a le type le plus dérivé dont le
constructeur ou destructeur en cours d'exécution détermine le
fonctionnement dynamique (ou type dynamique).


J'ai un peu mal à comprendre ce que tu dis là, mais j'ai plutôt
l'impression que c'est à peu près ce que je dis depuis le début. Le
comportement, c'est comme si le type le plus dérivé (donc, le type
dynamique de l'objet) est celui du constructeur en cour d'exécution.

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