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
James Kanze
Laurent Deniau writes:

|> wrote:
|> > Laurent Deniau wrote in message
|> >>Donc quand Gaby dit que un B* devient un A* en entrant dans A::~A(),
|> >>il a complement raison.

|> > Un B* devient un A* dans toute fonction member de A. Quel rapport
|> > ?

|> Je parle de type effectif, ou dynamique comme tu le dis. Je ne
|> savais pas que les __vtbl des objets etaient mis a jour quand ils
|> transitaient a travers leurs ctors/dtor (est-ce que j'etais le seul
|> a ne pas le savoir?). J'ai donc insite sur ce fait.

Ce n'est pas l'« effectif » qui me gène en ce que tu dis. C'est que tel
que c'est écrit, j'ai l'impression que tu dis que le type du pointeur
change. Le type du pointeur ne change pas ; ce n'est que le type
dynamique (ou effectif) de l'objet désigné par le pointeur qui change.

|> > Quand il y a résolution dynamique d'une fonction, la fonction
|> > appelée dépend du type dynamique de l'objet pointé, non du type du
|> > pointeur. Ce qui importe ici, c'est que le type dynamique de
|> > l'objet change quand on entre dans le destructeur. Le type du
|> > pointeur n'a pas beaucoup d'importance.

|> on peut le dire comme ca. mais la distinction:

|> this statique A* dynamique A*
|> et
|> this statique A* dynamique B*

Non. Le type (statique ou dynamique) de this ne change pas. Ce qui
pourrait changé, c'est le type dynamique de l'expression *this.

|> n'etait evidente dans tes propos. J'ai plutot compris que tu disais
|> que le compilateur changeait son mode de resolution des appels
|> virtuels quand il entrait dans un ctor/dtor.

C'est ce qu'il fait. Enfin, pas le mode, mais 1) il change bien le vptr,
afin que le type dynamique change, et 2) on se trouve systèmatiquement
dans le cas où le compilateur connaît le type dynamique, ce qui le
permet d'optimiser l'appel assez souvent.

|> >>Ce qui veut dire ensuite que la resolution des methodes (virtuelle
|> >>ou non) reste inchange: pas de semantique particuliere dans les
|> >>ctor/dtor.

|> > Tout à fait. On résoud l'appel selon le type dynamique de l'objet,
|> > et non selon le type du pointeur.

|> c'est ce qui etait confus dans tes propositions de regles. La
|> derniere formulee simplement aurait suffit.

OK.

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

|> wrote:
|> > Laurent Deniau wrote in message
|> > news:<co73ak$p5m$...

|> >>j'espere que tu n'as pas l'intension d'utiliser un GC un jour ;-)

|> > Quel rapport ? D'abord, le GC fait partie de l'implémentation --
|> > c'est

|> Parce que tu utilises un pointeur membre qui pointe sur une autre
|> partie de l'instance, le tout dans le meme objet (au sens storage du
|> terme).

|> Les GC n'aiment pas ca.

Quelles GC ne l'aiment pas. Ça ne pose pas de problèmes pour celles que
je connais.

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

| Laurent Deniau writes:
|
| |> wrote:
| |> > Laurent Deniau wrote in message
| |> >>Donc quand Gaby dit que un B* devient un A* en entrant dans A::~A(),
| |> >>il a complement raison.
|
| |> > Un B* devient un A* dans toute fonction member de A. Quel rapport
| |> > ?
|
| |> Je parle de type effectif, ou dynamique comme tu le dis. Je ne
| |> savais pas que les __vtbl des objets etaient mis a jour quand ils
| |> transitaient a travers leurs ctors/dtor (est-ce que j'etais le seul
| |> a ne pas le savoir?). J'ai donc insite sur ce fait.
|
| Ce n'est pas l'« effectif » qui me gène en ce que tu dis. C'est que tel
| que c'est écrit, j'ai l'impression que tu dis que le type du pointeur
| change. Le type du pointeur ne change pas ; ce n'est que le type
| dynamique (ou effectif) de l'objet désigné par le pointeur qui change.

Non plus.

-- Gaby
Avatar
Gabriel Dos Reis
Arnaud Meurgues writes:

| drkm wrote:
|
| > Mais le type dynamique de « this » est égal à son type statique.
| > Pas celui de « * this ».
|
| Tu veux dire que
| this->xxx
| et
| (*this).xxx
| ne sont pas toujours équivalents ?!

Non. Il veut juste dire que « this » est une rvalue donc ne voit pas
son type dynamique changer ; mais « this-> » et « (*this). » restent
équivalentes -- donc peuvent jouer sur la notion de type dynamique.

-- Gaby
Avatar
Arnaud Meurgues
Gabriel Dos Reis wrote:

Les expressions « type statique » et « type dynamique » ne sont
formellement définies que pour les « object expressions », i.e. les
expressions qui désignent des objets.


Ok.

Non, pA aura toujours comme type dynamique "A*", mais l'expression
"*pA" pourrait avoir un type différent -- je suppose que c'est ce qu
tu as voulu dire.


Effectivement. Dans ce genre de discussion, le manque de rigueur ne
pardonne pas.

--
Arnaud
(Supprimez les geneurs pour me répondre)

Avatar
kanze
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.

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.

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

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), la réponse serait bien oui. En
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.

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

--
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
Arnaud Meurgues wrote in message
news:<41ab1fc0$0$6644$...
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.


Justement non. Le type de l'objet évolue pendant la construction.
Formellement, la durée de vie de l'objet ne commence qu'une fois qu'on
sort du constructeur de l'objet complet, mais pratiquement, on peut bien
se servir de l'objet, en tant qu'objet, avant, et alors, il se comporte
comme s'il avait le type du constructeur qui est en cours d'exécution.

En revanche, on peut parler du type statique d'une variable et du type
dynamique d'une variable.


Non. Une variable a un type fixe et invariable. Tout au plus on peut
dire que l'objet défini par la variable n'a pas commencé sa durée de
vie.

Une variable pA définie par
A* pA;
a un type statique A*
et un type dynamique qui dépend de l'exécution, qui pourra être A* ou
un pointeur sur une sous-classe quelconque de A.


Le type de pA est toujours A*. Que ce soit le type statique ou le type
dynamique. C'est le type de l'expression *pA qui pourrait varier, non le
type de la variable.

Mais parler de "type dynamique d'un objet" ne me semble pas avoir
grande signification.


L'expression *pA désigne un objet. On peut bien parlé du type dynamique
de l'objet désigné par *pA. Et que ce type peut bien varier en cours de
l'exécution du programme, soit parce que l'expression désigne un autre
objet, soit parce que l'objet désigné est à des étapes différentes de
construction.

--
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:...
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. Et c'est plus compliqué que ça, parce que la
«@définnition » est complétée par d'autres clauses de la norme.

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

--
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
Laurent Deniau wrote in message
news:...
drkm wrote:
Gabriel Dos Reis writes:

writes:

| qui importe ici, c'est que le type dynamique de l'objet change
| quand on entre dans le destructeur. Le type du pointeur n'a pas
| beaucoup d'importance.

Pour la n-ième fois, le type dynamique d el'objet ne change pas.




Pour clarifier : selon la norme, l'objet n'existe pas avant d'avoir
sortie du constructeur de l'objet complet (voir §3.8). Mais des règles
spéciales nous permettent certaines opérations sur l'objet lorsqu'on est
en train d'en exécuter les constructeurs et les destructeurs (voir §12.6
et §12.7). Et quand on fait ces opérations, l'objet a un comportement
comme si son type dynamique était le type du constructeur en cours.
Donc, le type dynamique ne change pas, parce que l'objet n'existe pas,
mais on peut utiliser l'objet qui n'existait pas comme si c'était un
objet qui existait avec le type dynamique du constructeur ou du
destructeur en cours -- donc, avec un type dynamique qui varie.

Si je comprend bien, tu dis que, que l'on soit dans le processus
de création d'un objet dérivé ou non, à partir du moment où l'on est
dans le destructeur, on sait que le type dynamique de l'objet est
égal au type statique. Car même si l'on est dans le processus de
destruction d'un objet dérivé, à ce moment là, les parties
spécifiques de l'objet dérivé ont été détruites, et l'on n'a plus
qu'on objet du type statique du destructeur dans lequel on se
trouve. Dans un destructeur, on connait exactement le type de
l'objet que l'on et en train de détruire.

Et vice-versa pour les constructeurs.


Ce qui est confus dans les propos de James, c'est qu'il utilise le
terme "type dynamique" pour parler du changement des pointeurs __vtbl
dans les sous-objets.


Où ça ? Je n'ai jamais dit quoique ce soit des pointeurs __vtbl ou
d'autres.

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

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.

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

| Arnaud Meurgues wrote in message
| news:<41ab1fc0$0$6644$...
| > 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.
|
| Justement non. Le type de l'objet évolue pendant la construction.
^

Tu veux dire le type de l'objet le *plus dérivé*. Pendant la
construction, ce sont les parties entièrement construites où dont les
constructeurs sont en cours qui déterminent ce qui va être l'objet le
dérivé du moment.

1.8/2
Objects can contain other objects, called sub-objects. A sub-object
can be a member sub-object (9.2), a base class sub-object (clause
10), or an array element. An object that is not a sub-object of any
other object is called a complete object.

1.8/3
For every object x, there is some object called the complete object
of x, determined as follows:
-- If x is a complete object, then x is the complete object of x.
-- Otherwise, the complete object of x is the complete object of
the (unique) object that contains x.

1.8/4
If a complete object, a data member (9.2), or an array element is
of class type, its type is considered the most derived class, to
distinguish it from the class type of any base class subobject; an
object of a most derived class type is called a most derived object.


| Formellement, la durée de vie de l'objet ne commence qu'une fois qu'on
| sort du constructeur de l'objet complet, mais pratiquement, on peut bien
| se servir de l'objet, en tant qu'objet, avant, et alors, il se comporte
| comme s'il avait le type du constructeur qui est en cours d'exécution.

dans des conditions bien délimitées.

-- Gaby