Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

'this' : used in base member initializer list

80 réponses
Avatar
amerio
Bonjour,
Mon pb doit est le suivant : une classe doit initialisé un de ses membres
avec un pointeur dur elle même :

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

class A
{
public:
A() : m_B(this) {} // warning 'this' : used in base member
initializer list
private:
const B m_B;
}

Comment eviter le warning (sous VC6) (et d'abord, dois je l'éviter ou
changer de technique ? quel est le risque ?)
Au passage, boost:: utilise cette technique, cf
http://www.boost.org/libs/smart_ptr/sp_techniques.html#weak_without_shared

10 réponses

1 2 3 4 5
Avatar
David Brabant
"amerio" wrote

Comment eviter le warning (sous VC6)


#pragma warning(disable: 4355)

--
David

Avatar
amerio
Comment eviter le warning (sous VC6)


#pragma warning(disable: 4355)


Humm, oui, ;-), mais ce n'etait pas là ma question.
C'etait plutot : est-ce dangereux (ou non-portable) ?


Avatar
drkm
"amerio" writes:

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


C'est quoi exactement, ce truc ? Avec "typename" j'aurais compris,
mais tel quel, je ne vois pas à quoi ça correspond.


Ecrire class A; avant c'est pareil que redire que A est une classe à
chaque fois (ou typename, c'est kifkif) (plus lourd, peut etre, mais
là l'est pas mon pb !) Mon pb, c'est de savoir si oui ou non il est
risqué (ou non portable) d'utiliser 'this' dans la liste
d'initialisation des membres.


Oui, cfr. le message :

Subject: Utilisation de `this´ dans `ctor-initializer´
Date: 20 May 2003 04:17:15 +0200
Message-ID:

En même temps, en cherchant un peu dans la norme, j'ai trouvé ceci :

9.3.2/1

In the body of a nonstatic (9.3) member function, the keyword
`this´ is a non-lvalue expression whose value is the address of
the object for which the function is called.

12.6.2/4

[...] if a member of X is neither specified in the constructor's
`mem-initializers´, nor default-initialized, nor initialized
during execution of the body of the constructor [...]

Le premier extrait dit clairement que `this´ est utilisable dans le
*corps* d'une fonction membre non-statique, alors que le second
/semble/ faire une différence entre le corps d'un constructeur et son
`ctor-initializer´.

Je n'ai cependant pas réussi à trouver un passage clair sur
l'appartenance ou non du `ctor-initializer´ au corps de son
constructeur. S'il devait s'avérer qu'il n'y appartient pas, il ne
serait alors pas possible d'y utiliser `this´, non ?

--drkm



Avatar
drkm
"amerio" writes:

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


C'est quoi exactement, ce truc ? Avec "typename" j'aurais compris,
mais tel quel, je ne vois pas à quoi ça correspond.


Ecrire class A; avant c'est pareil que redire que A est une classe à
chaque fois (ou typename, c'est kifkif) (plus lourd, peut etre, mais
là l'est pas mon pb !) Mon pb, c'est de savoir si oui ou non il est
risqué (ou non portable) d'utiliser 'this' dans la liste
d'initialisation des membres.


J'ajouterais que le code suivant compile parfaitement sous GCC, sans
diagnostique :

cat fclc++.cc
#include <iostream>


class B
{
public:
B( class A* pA ) : m_pA( pA ) { }

class A* m_pA ;
} ;

class A
{
public:
A() : m_B( this ) { }

const B m_B ;
} ;

int main()
{
A a ;

std::cout << a.m_B.m_pA << ' ' << & a << std::endl ;

return 0 ;
}

g++ --version
g++ (GCC) 3.2 20020818 (prerelease)

Copyright (C) 2002 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.

g++ -o fclc++ fclc++.cc -Wall -ansi -pedantic
./fclc++
0x76fd54 0x76fd54


--drkm



Avatar
kanze
drkm wrote in message news:...
"amerio" writes:

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


C'est quoi exactement, ce truc ? Avec "typename" j'aurais compris,
mais tel quel, je ne vois pas à quoi ça correspond.


Ecrire class A; avant c'est pareil que redire que A est une classe à
chaque fois (ou typename, c'est kifkif) (plus lourd, peut etre, mais
là l'est pas mon pb !) Mon pb, c'est de savoir si oui ou non il est
risqué (ou non portable) d'utiliser 'this' dans la liste
d'initialisation des membres.


J'ajouterais que le code suivant compile parfaitement sous GCC, sans
diagnostique :


Et pourtant, ce n'est pas légal:-).

cat fclc++.cc
#include <iostream>



Tu as oublié l'« #include <ostream> ». Les opérateurs << ne sont pas
définis.

class B
{
public:
B( class A* pA ) : m_pA( pA ) { }

class A* m_pA ;
} ;

class A
{
public:
A() : m_B( this ) { }

const B m_B ;
} ;


Ajoutons un:

class C : public A
{
public:
C() : unDeuxiemeB( this ) {}

B const unDeuxiemeB ;
} ;

G++ ne dit toujours rien. Et pourtant, c'est illégal. La conversion de
this (un C*) en A* a un comportement indéfini.

En fait, il y a de fortes chances que ça marche, tant qu'il n'y a pas
d'héritage virtuel. Mais la norme l'interdit explicitement.

int main()
{
A a ;

std::cout << a.m_B.m_pA << ' ' << & a << std::endl ;

return 0 ;
}

g++ --version
g++ (GCC) 3.2 20020818 (prerelease)

Copyright (C) 2002 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.

g++ -o fclc++ fclc++.cc -Wall -ansi -pedantic
./fclc++
0x76fd54 0x76fd54



Le fait que quelque chose marche avec un compilateur donné ne prouve pas
qu'il soit légal. Dans le cas en question, c'est légal, mais seulement
parce que le constructeur qui a reçu le paramètre ne fait que le copier.
Changer ce constructeur, g++ ne râlera toujours pas, mais le code ne
serait plus correct.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16




Avatar
drkm
writes:

drkm wrote in message
news:...

J'ajouterais que le code suivant compile parfaitement sous GCC,
sans diagnostique :


Et pourtant, ce n'est pas légal:-).

cat fclc++.cc
#include <iostream>



Tu as oublié l'« #include <ostream> ». Les opérateurs << ne sont pas
définis.


Yep. Je ne prend jamais la « peine » de l'inclure dans des petits
tests rapides, et bien sûr, ne pense pas à l'ajouter si je poste le
code :-(.

class B
{
public:
B( class A* pA ) : m_pA( pA ) { }

class A* m_pA ;
} ;

class A
{
public:
A() : m_B( this ) { }

const B m_B ;
} ;


Ajoutons un:

class C : public A
{
public:
C() : unDeuxiemeB( this ) {}

B const unDeuxiemeB ;
} ;

G++ ne dit toujours rien. Et pourtant, c'est illégal. La conversion
de this (un C*) en A* a un comportement indéfini.


Yep.

En fait, il y a de fortes chances que ça marche, tant qu'il n'y a
pas d'héritage virtuel. Mais la norme l'interdit explicitement.


Je ne vois pas exactement comment l'héritage virtuel complique
l'affaire. Lors d'une conversion entre deux types d'une même
hiérarchie avec héritage virtuel, il est possible que le compilateur
doive ajouter ou retrancher un petit décalage à la valeur du pointeur.
Et si je comprend bien, la valeur de ce décalage est connue dès que
les types sont connus (ce qui est le cas dans une conversion).

Si l'on a l'adresse d'un objet, on peux calculer celle d'un
sous-objet ou sur-objet, indépendamment que ces objets soient ou non
construits (les adresses sont donc plutôt celles d'endroits où se
*trouveraient* les objets).

Mais je ne suis pas du tout spécialiste en ces affaires. Ai-je
manqué une marche ?

int main()
{
A a ;

std::cout << a.m_B.m_pA << ' ' << & a << std::endl ;

return 0 ;
}

g++ --version
g++ (GCC) 3.2 20020818 (prerelease)

Copyright (C) 2002 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.

g++ -o fclc++ fclc++.cc -Wall -ansi -pedantic
./fclc++
0x76fd54 0x76fd54



Le fait que quelque chose marche avec un compilateur donné ne prouve
pas qu'il soit légal. Dans le cas en question, c'est légal, mais
seulement parce que le constructeur qui a reçu le paramètre ne fait
que le copier. Changer ce constructeur, g++ ne râlera toujours pas,
mais le code ne serait plus correct.


Bien sûr. C'est pour cela que l'article commence par « J'ajouterais
que le code suivant ... », en complément de l'article que j'avais
posté juste avant. Il ne voulais bien sûr pas servir de preuve, mais
plutôt d'illustration du comportement d'un autre compilateur.

--drkm



Avatar
drkm
writes:

drkm wrote in message
news:...

En même temps, en cherchant un peu dans la norme, j'ai trouvé
ceci :

9.3.2/1

In the body of a nonstatic (9.3) member function, the keyword
`this´ is a non-lvalue expression whose value is the address
of the object for which the function is called.

12.6.2/4

[...] if a member of X is neither specified in the
constructor's `mem-initializers´, nor default-initialized, nor
initialized during execution of the body of the constructor
[...]

Le premier extrait dit clairement que `this´ est utilisable dans
le *corps* d'une fonction membre non-statique, alors que le second
/semble/ faire une différence entre le corps d'un constructeur et
son `ctor-initializer´.

Je n'ai cependant pas réussi à trouver un passage clair sur
l'appartenance ou non du `ctor-initializer´ au corps de son
constructeur. S'il devait s'avérer qu'il n'y appartient pas, il ne
serait alors pas possible d'y utiliser `this´, non ?


Ce qui suggèrerait que l'utilisation de this dans la liste
d'initialisation serait illégale.


Oui. Si le `ctor-initializer´ ne fait pas partie du corps du
constructeur.

Tous les compilateurs le permettent, et je suis assez sûr que
c'était l'intention du comité de le permettra.


C'est ce que j'imagine. Mais ce ne serait pas, je pense, le premier
exemple de contradiction entre l'intention du comité et sa rédaction
dans la norme ...

D'autant que je ne vois pas de raison pour un compilateur de
connaître la valeur de `this´ dans le corps du constructeur (je veux
dire après l'accolade ouvrante) et non dans la liste d'initialistion.

Par la suite, il y a la question de ce qu'on peut faire avec this à
ce moment. Dans la liste d'intialisation, this est un pointeur à un
objet non encore construit. Ça nous amène à §3.8/5, qui précise ce
qu'on peut faire avec un tel pointeur.


Je comprend l'interdiction de conversion d'un pointeur vers un objet
de type non-POD, dans le cas d'opérateurs de conversion définis par
l'utilisateur. Mais je ne comprend pas la motivation derrière
l'interdiction de la conversion vers un type de classe de base. Comme
je l'ai dis dans mon dernier article, il me semble que la conversion
d'adresses d'objets dans la même hiérarchie ne requiérerait [*] pas
que les objets soient construits.

[*] Je parle du point de vue de ce que requérerait une
implémentation, non de ce que requière la norme.

Une des choses qu'on peut faire, c'est le copier -- le passer en
tant que paramétre est donc authorisé, et garanti à fonctionner. En
revanche, si on le passe, on peut supposer que le constructeur
auquel on l'a passé va en faire quelque chose avec lui. Si tout ce
qu'il fait, c'est de le copier, pour l'utiliser plut tard (comme
c'est le cas ici), pas de problème. S'il fait quelque chose de plus,
ne serait-ce que de le convertir en un pointeur à une classe de
base, il y a un comportement indéfini. C'est d'ailleur le sens de
l'avertissement de VC++ : a priori, tu ne sais pas ce que fait la
classe avec le pointeur que tu lui passe. C'est donc une source
potentielle d'un comportement indéfini.

Dans ce cas-ci, l'idiome est assez courant. Assez pour que je crois
Microsoft a eu tort d'émettre un avertissment, même si ça part d'un
bon sentiment.


Du moins, de l'émettre sans options particulières. Je le verrais
bien dans un mode « full warnings ». Un peu comme `-Weffc++´ peut
avec GCC nous submerger d'avertissements inutiles, ou en émettre de
très pertinents.

Mais j'aimerais savoir ce qu'il en est pour l'appartenance du
`ctor-initializer´ au corps du constructeur. Quelqu'un a-t-il une
piste ?

--drkm


Avatar
kanze
drkm wrote in message
news:...
writes:
drkm wrote in message
news:...

En même temps, en cherchant un peu dans la norme, j'ai trouvé
ceci :

9.3.2/1

In the body of a nonstatic (9.3) member function, the keyword
`this´ is a non-lvalue expression whose value is the address
of the object for which the function is called.

12.6.2/4

[...] if a member of X is neither specified in the
constructor's `mem-initializers´, nor default-initialized, nor
initialized during execution of the body of the constructor
[...]

Le premier extrait dit clairement que `this´ est utilisable dans
le *corps* d'une fonction membre non-statique, alors que le second
/semble/ faire une différence entre le corps d'un constructeur et
son `ctor-initializer´.

Je n'ai cependant pas réussi à trouver un passage clair sur
l'appartenance ou non du `ctor-initializer´ au corps de son
constructeur. S'il devait s'avérer qu'il n'y appartient pas, il ne
serait alors pas possible d'y utiliser `this´, non ?


Ce qui suggèrerait que l'utilisation de this dans la liste
d'initialisation serait illégale.


Oui. Si le `ctor-initializer´ ne fait pas partie du corps du
constructeur.


Ce que je crains, c'est que c'est parfois oui, parfois non, selon
l'auteur exact du passage.

Tous les compilateurs le permettent, et je suis assez sûr que
c'était l'intention du comité de le permettra.


C'est ce que j'imagine. Mais ce ne serait pas, je pense, le premier
exemple de contradiction entre l'intention du comité et sa rédaction
dans la norme ...


Certes. C'est ce qui arrive quand on veut faire trop, trop vite.

D'autant que je ne vois pas de raison pour un compilateur de
connaître la valeur de `this´ dans le corps du constructeur (je veux
dire après l'accolade ouvrante) et non dans la liste d'initialistion.


S'il ne connaît pas this dans la liste d'initialisation, comment fait-il
à passer le this aux constructeurs des sous-objets ?

Par la suite, il y a la question de ce qu'on peut faire avec this à
ce moment. Dans la liste d'intialisation, this est un pointeur à un
objet non encore construit. Ça nous amène à §3.8/5, qui précise ce
qu'on peut faire avec un tel pointeur.


Je comprend l'interdiction de conversion d'un pointeur vers un objet
de type non-POD, dans le cas d'opérateurs de conversion définis par
l'utilisateur. Mais je ne comprend pas la motivation derrière
l'interdiction de la conversion vers un type de classe de base. Comme
je l'ai dis dans mon dernier article, il me semble que la conversion
d'adresses d'objets dans la même hiérarchie ne requiérerait [*] pas
que les objets soient construits.

[*] Je parle du point de vue de ce que requérerait une
implémentation, non de ce que requière la norme.


En es-tu sûr ? Même dans le cas de l'héritage virtuel ? Même pour un
pointeur arbitraire. Parce que le langage ici parle d'un pointeur *vers*
l'objet ; on parle de this uniquement parce que c'est un pointeur vers
l'objet, et qu'il n'y a pas de régles pour le traiter à part.

Une des choses qu'on peut faire, c'est le copier -- le passer en
tant que paramétre est donc authorisé, et garanti à fonctionner. En
revanche, si on le passe, on peut supposer que le constructeur
auquel on l'a passé va en faire quelque chose avec lui. Si tout ce
qu'il fait, c'est de le copier, pour l'utiliser plut tard (comme
c'est le cas ici), pas de problème. S'il fait quelque chose de plus,
ne serait-ce que de le convertir en un pointeur à une classe de
base, il y a un comportement indéfini. C'est d'ailleur le sens de
l'avertissement de VC++ : a priori, tu ne sais pas ce que fait la
classe avec le pointeur que tu lui passe. C'est donc une source
potentielle d'un comportement indéfini.

Dans ce cas-ci, l'idiome est assez courant. Assez pour que je crois
Microsoft a eu tort d'émettre un avertissment, même si ça part d'un
bon sentiment.


Du moins, de l'émettre sans options particulières. Je le verrais
bien dans un mode « full warnings ». Un peu comme `-Weffc++´ peut
avec GCC nous submerger d'avertissements inutiles, ou en émettre de
très pertinents.


Je ne sais pas. Dans la doute, je crois que mettre un maximum
d'avertissements par défaut, ce n'est peut-être pas une mauvaise
politique. Après tout, le programmeur qui sait ce qu'il fait sait qu'il
faut lire la documentation, et spécifier exactement des avertissements
dont il a besoin.

Mais j'aimerais savoir ce qu'il en est pour l'appartenance du
`ctor-initializer´ au corps du constructeur. Quelqu'un a-t-il une
piste ?


Il faudra peut-être démander dans comp.std.c++.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16



Avatar
kanze
drkm wrote in message
news:...
writes:

drkm wrote in message
news:...

J'ajouterais que le code suivant compile parfaitement sous GCC,
sans diagnostique :


Et pourtant, ce n'est pas légal:-).

cat fclc++.cc
#include <iostream>



Tu as oublié l'« #include <ostream> ». Les opérateurs << ne sont pas
définis.


Yep. Je ne prend jamais la « peine » de l'inclure dans des petits
tests rapides, et bien sûr, ne pense pas à l'ajouter si je poste le
code :-(.


C'est seulement pour dire que le fait qu'il compile n'en prouve pas la
légalité.

class B
{
public:
B( class A* pA ) : m_pA( pA ) { }

class A* m_pA ;
} ;

class A
{
public:
A() : m_B( this ) { }

const B m_B ;
} ;


Ajoutons un:

class C : public A
{
public:
C() : unDeuxiemeB( this ) {}

B const unDeuxiemeB ;
} ;

G++ ne dit toujours rien. Et pourtant, c'est illégal. La conversion
de this (un C*) en A* a un comportement indéfini.


Yep.

En fait, il y a de fortes chances que ça marche, tant qu'il n'y a
pas d'héritage virtuel. Mais la norme l'interdit explicitement.


Je ne vois pas exactement comment l'héritage virtuel complique
l'affaire. Lors d'une conversion entre deux types d'une même
hiérarchie avec héritage virtuel, il est possible que le compilateur
doive ajouter ou retrancher un petit décalage à la valeur du pointeur.
Et si je comprend bien, la valeur de ce décalage est connue dès que
les types sont connus (ce qui est le cas dans une conversion).


Le problème avec l'héritage virtuel, c'est que ce décalage dépend du
type le plus dérivé, à construire. On ne peut pas le savoir d'avance. Ce
n'est pas une constante de compilation pour la classe C, parce que si je
dérive un D de C, le décalage dans le C dans le D serait différent du
décalage du C tout seul.

En ce qui concerne le this, il faut que le compilateur ait
l'information, parce que dans le corps proprement dit du constructeur
(c-à-d sans la liste d'initialisation), il faut pouvoir appeler des
fonctions de la classe de base. Mais le constructeur de C n'est pas
obligé à mettre cette information à un endroit où d'autres peuvent y
accéder avant d'arriver dans son corps proprement dit. Convertir un
pointeur autre que this, ailleurs, pourrait ne pas fonctionner
correctement. Et la régle parle des pointeurs à l'objet, et non
spécifiquement de this. (En fait, si ça marche dépend de la façon que le
compilateur gère des classes de base virtuelles.)

Si l'on a l'adresse d'un objet, on peux calculer celle d'un
sous-objet ou sur-objet, indépendamment que ces objets soient ou non
construits (les adresses sont donc plutôt celles d'endroits où se
*trouveraient* les objets).


Pas dans le cas des bases virtuelles. Il te faut une information
dynamique, que tu trouves soit dans le vtable (et donc, à travers le
vptr), soit dans la classe même. Or, c'est le constructeur qui
initialise le vptr ou les autres informations dans la classe.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16




Avatar
darkman_spam
wrote in message
news:...

C'est seulement pour dire que le fait qu'il compile n'en prouve pas
la légalité.


Yep, comme toujours. Ceci n'était qu'un complément à un autre
article posté parallèlement (et un peu trop vite).

Je ne vois pas exactement comment l'héritage virtuel complique
l'affaire.


Le problème avec l'héritage virtuel, c'est que ce décalage dépend du
type le plus dérivé, à construire.


Ok. Je n'avais pas pensé à ce fait. Je pensais (un peu vite) que
le décalage était tout le temps le même pour deux mêmes types,
indépendamment du fait qu'ils soient des sous-objets de différents
types.

Je comprend mieux, maintenant.

--drkm


1 2 3 4 5