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

Polymorphisme, sémantique de valeur, durée de vie des temporaires...

38 réponses
Avatar
Marc Boyer
Bonjour,

j'ai un problème 'pure virtual method called' (avec g++), et je n'arrive
pas à faire d'exemple minimal qui reproduise le pb...

Mais j'ai peut etre identifié le pb. Quand j'écris un truc du genre
X x( Y(1) );
avec X et Y deux classes, quelle est la durée de vie de l'objet
temporaire Y ?

Actuellement, je prends une référence sur cet objet, et visiblement
il aime pas. Alors que si j'écris
X x( *(new Y(1)) );
ça marche bien.

Merci,

Marc Boyer
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. André Maurois)

10 réponses

1 2 3 4
Avatar
James Kanze
On Mar 7, 12:11 pm, Laurent Deniau wrote:
Marc Boyer wrote:
Marc Boyer writes:
[...]



Mais j'ai peut etre identifié le pb. Quand j'écris un truc du gen re
X x( Y(1) );
avec X et Y deux classes, quelle est la durée de vie de l'objet
temporaire Y ?
Comme tous les temporaires qui ne sont pas bindé a une variable refe rence:

jusqu'a la fin de l'expression.


Oui, et ici, l'expression termine à la fin du constructeur de x( );



Avant, même. x() ne fait partie d'aucune expression ; il n'y a
que le contenu des parenthèses qui sont une expression. (Mais il
y a une extension de la durée de vie ici aussi... jusqu'au
retour du constructeur d'x.)

Actuellement, je prends une référence sur cet objet,
Si tu la conserve au dela du constructeur de x (comme le laisse croire la

suite), c'est fort possible que ce soit la source de ton probleme (et on ne
se retrouve pas dans le cas general mais vraissemblablement tu appelle s le
membre virtuel apres la fin de l'execution du destructeur de Y(1)).


Oui, c'est ça. Ce qui est déstabilisant, c'est que ça fait part ie
des trucs qui arrivent 'parfois'. En fait, j'avais fait un exemple
minimal à poster ici, et sur l'exemple, ça plante pas.


Probablement parce que ton compilateur ne detruit les temporaires qu'a
la fin du bloc main.


Il a dit que c'est g++, non CFront.

C'est plutôt que le compilateur voit que changer le vptr pour le
destructeur ici ne sert à rien, parce que le destructeur est
vide. Et donc, il le laisse. Si le destructeur de la classe de
base n'est pas vide, en revanche, g++ change bien le vptr pour
pointeur à la vtbl de la classe de base. Et le comportement
qu'il a, c'est bien ce que fait g++ quand on appelle une
fonction virtuelle pûre à travers la vtbl de la classe de base.

--
James Kanze (GABI Software) email:
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
Sylvain
Marc Boyer wrote on 07/03/2007 16:08:

OUi, mais c'est toujours Fonction::() qui est appelé.


pour une instance de Fonction oui, pour une instance (effective, pas
détruite) de Affine, non.

Mais il marche très bien actuellement ;-)
Il est laid, mais il marche.


hmmm

Marc Boyer wrote on 07/03/2007 16:23:
Bon, je n'arrive plus à avoir les deux comportements différents :-(
Ca plante dans tous les cas. C'est plutot normal.


si tu t'attends à ce qu'il "plante" alors en effet il "marche"
mais il va bien tôt falloir préciser les termes.

Sylvain.

Avatar
James Kanze
On Mar 7, 3:45 pm, Jean-Marc Bourguet wrote:
Marc Boyer writes:
Marc Boyer writes:

Si le véritable exemple t'intéresse, je peux te le poster.
Ca n'est pas très long (7 classes, 400 lignes, 10ko de code).


Tu peux le faire ou me l'envoyer en perso. Je ne garanti pas de repo nse
rapide.


Levons une ambiguïté: en ce qui me concerne, l'explication sur
la gestion des temporaires me convient, en j'ai une solution à
base de *(new X(1)), qui crée des objets persistants.
Après, Laurent était étonné par la nature du message d'erreur et
je lui proposais le code s'il le voulait. Cela vaut aussi pour
toi.
Mais ne fait pas ça pour moi, mais uniquement pour satisfaire ta
curiosité.


J'aimerais bien comprendre pourquoi ton exemple reduit ne donne pas le
message que tu as avec le code complet...


C'est un comportement indéfini. Pourquoi veux-tu qu'il y ait une
logique.

Et ce avec au moins 2
compilateurs d'origine differentes meme en non optimise, donc il y a peut
de chance que ce soit un artefact d'un compilateur qui applique une
optimisation tenant compte du fait que tout est visible.


C'est une optimisation dans g++. J'ai eu l'occasion de le
constater par la passée, g++ ne change pas le vptr dans le
constructeur s'il peut déterminer que ça ne change pas le
comportement du code (si par exemple il peut établir qu'il n'y a
pas d'appel d'une fonction virtuelle, ni d'utilisation de
dynamic_cast ou de typeid).

--
James Kanze (GABI Software) email:
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
Marc Boyer
Le 07-03-2007, Sylvain a écrit :
Marc Boyer wrote on 07/03/2007 16:08:
OUi, mais c'est toujours Fonction::() qui est appelé.


pour une instance de Fonction oui, pour une instance (effective, pas
détruite) de Affine, non.


Reprenons
-------------------------- ECM -----------------------------------------
#include <algorithm>
#include <cassert>

struct Fonction {
virtual double operator()(double x) const { assert(false); return 0;} ;
// La fonction virtuelle pure remplacee par une fct reelle pour
// que ça compile
virtual ~Fonction(){};
};

class Affine : public Fonction {
double a;
public:
Affine(double a):a(a){};
double operator()(double x) const {
return a*x;
}
};

class Min : public Fonction {
const Fonction f;
const Fonction g;
// J'utilise des valeurs, et non des références, puisque c'est le
// point de débat (cf
// )
public:
Min(const Fonction& f, const Fonction& g):
f(f), g(g){};
double operator()(double x) const {
return std::min( f(x), g(x) );
}
};

int main(){
Min m( Affine(2), Affine(3) );
assert( m(1)== 2 );
}

% ./fclcpp
fclcpp: fclcpp.cpp:5: virtual double Fonction::operator()(double) const:
Assertion `false' failed.
-------------------------- ECM -----------------------------------------

Donc, c'est bien Fonction:() qui est appelé et pas Affine,
même si je remplace
Min m( Affine(2), Affine(3) );
par
Min m( *new Affine(2), *new Affine(3) );


Marc Boyer wrote on 07/03/2007 16:23:
Bon, je n'arrive plus à avoir les deux comportements différents :-(
Ca plante dans tous les cas. C'est plutot normal.


si tu t'attends à ce qu'il "plante" alors en effet il "marche"
mais il va bien tôt falloir préciser les termes.


J'ai peut-être manqué de clarté.
Il plante quand je lui passe des temporaires en paramètre, dans
l'exemple complet et l'exemple minimal. Ce qui me semble normal.

Après, quand je l'appelle avec des non temporaires, genre
Affine a1(1);
Affine a2(2);
Min m(a1,a2);
Min m2( *(new Affine(3)), *(new Affine(4));
ça fonctionne très bien, ça trace les courbes et ça valide tout ce
qui faut. J'ajoute un petit
#define P(X) (*(new X))
car je suis un peu faignéant...


Marc Boyer
PS: Faut dire, j'exagère un peu de faire ça en C++. On m'avait plutot
conseillé Matlab, mais je faisais trop d'erreurs de syntaxe...
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. André Maurois)


Avatar
Marc Boyer
Le 07-03-2007, James Kanze a écrit :
Alors je pourrais sortir l'artillerie des pointeurs, ou de methode
..clone(), mais j'ai la flemme. C'est du code jetable.


des données membre pointeurs sont pourtant la bonne solution, leur
initialisation, utilisation peut selon le nombre d'occurrence être
doublonné avec des refs pour s'épargner qlq déréférencements.


Dans de tels cas, ça m'arrive souvent de faire que le paramètre
et la variable membre sont des std::auto_ptr. Quand je fais
vite, et que je n'ai pas le collecteur de Boehm sous la main,
évidemment. Si j'ai Boost installé, je pourrais aussi préférer
boost::shared_ptr dans ce cas précis. (Ce n'est pas une réponse
universelle à l'absence du GC, mais dans le cas des agents
polymorphe, il est tout à fait indiqué.)


Mais dans les deux cas, à l'appel, il faudrait faire un appel
avec un new, non ?
Min m( new Affine(2), new Affine(3));

Marc Boyer
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. André Maurois)



Avatar
James Kanze
On Mar 7, 3:23 pm, Sylvain wrote:
Marc Boyer wrote on 07/03/2007 13:42:

class Min : public Fonction {
const Fonction& f;
const Fonction& g;
Probleme ici. Il ne faut pas utiliser des references mais des copies.




des copies locales ou des références à des non-temporaires.

Avec des copies
const Fonction f;
je perds le polymorphisme (et sur ce coup, il se plaint que Fonction
a une méthode virtuelle pure).


le polymorphisme existe toujours mais en effet 'Fonction' est
virtuel pure.


En fait, si Fonction est une classe abstraite (avec des
fonctions virtuelles pûre), il ne pourrait même pas en faire une
telle définition. Il ne peut pas exister des objets de type
Fonction en-dehors de la construction ou de la destruction des
objets d'un type dérivé.

Alors je pourrais sortir l'artillerie des pointeurs, ou de methode
..clone(), mais j'ai la flemme. C'est du code jetable.


des données membre pointeurs sont pourtant la bonne solution, leur
initialisation, utilisation peut selon le nombre d'occurrence être
doublonné avec des refs pour s'épargner qlq déréférencements.


Dans de tels cas, ça m'arrive souvent de faire que le paramètre
et la variable membre sont des std::auto_ptr. Quand je fais
vite, et que je n'ai pas le collecteur de Boehm sous la main,
évidemment. Si j'ai Boost installé, je pourrais aussi préférer
boost::shared_ptr dans ce cas précis. (Ce n'est pas une réponse
universelle à l'absence du GC, mais dans le cas des agents
polymorphe, il est tout à fait indiqué.)

int main(){
Min m( Affine(2), Affine(3) );
assert( m(1)== 2 );
Min m2( Min(Affine(2), Affine(3)), Affine(4) );
assert( m(1)== 2 );
}
En revanche, je ne vois pas le rapport avec 'pure virtual call' dans c et

exemple.



à la sortie du constructeur de 'm' les tempos Affine(2) et (3) sont
détruits et le destructeur peut (doit?) réduire sa table de résolut ion
virtuelle au type de base,


Lors de l'exécution du code du destructeur de base, il faut bien
que la table de résolution virtuelle soit celle du type de base.
La norme ne dit rien sur l'état de l'objet une fois qu'on a
sorti du destructeurs -- une implémentation de déboggage
pourrait zapper l'objet avec des 0, ou 0xdeadbeef, par exemple.
Le compilateur a le droit de supposer qu'il n'y a pas de tels
accès, et s'il peut déterminer que le comportement du code dans
le destructeur de base (y compris, évidemment, des fonctions
qu'il pourrait appeler) ne change pas si la table n'est pas
changée, il pourrait ne pas la changer, à titre d'optimisation.

m.f et m.g existent toujours et contiennent
les ghosts de ces classes lors de l'appel à m(1), la résolution
virtuelle de () appliquée à f et g tombe alors sur la définition de
Fonction () qui est bien virtuelle pure.


m.f et m.g n'existe pas en tant qu'objet. L'accès en est un
comportement indéfini. Ce qu'il voit (et ce que tu décris),
c'est un artifact de la façon que le compilateur génère le code.
On ne peut pas y compter.

--
James Kanze (GABI Software) email:
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
Sylvain
Marc Boyer wrote on 07/03/2007 17:19:

Reprenons


soit.

class Min : public Fonction {
const Fonction f;
const Fonction g;
// J'utilise des valeurs, et non des références, puisque c'est le
// point de débat (cf
// )


le post d'ID "" (1ier
post avec code) contient:

class Min : public Fonction {
const Fonction& f;
const Fonction& g;


"" est vu par TB comme un
email pas une ref. news, je ne peux donc pas vérifier (pas le temps de
les ouvrir en source un à un).

nonobstant

class Min : public Fonction {
const Fonction f;
const Fonction g;

ne peux pas (ne doit pas) compiler, puisqu'il impose la construction de
classes virtuelles pures; la question de son fonctionnement ne se pose
donc pas.

% ./fclcpp
fclcpp: fclcpp.cpp:5: virtual double Fonction::operator()(double) const:
Assertion `false' failed.
-------------------------- ECM -----------------------------------------

Donc, c'est bien Fonction:() qui est appelé et pas Affine,
même si je remplace
Min m( Affine(2), Affine(3) );
par
Min m( *new Affine(2), *new Affine(3) );


parlant de ce qui est imho non compilable, j'ai du mal à conjecturer...
soit tu amalgames différents essais, soit ton compilo est surprenant!

dans un "Fonction" il ne peut mettre qu'un "Fonction" (qui ne peut pas
exister).
dans un "Fonction&" ou "Fonction*" il mettra le "Affine&" reçue avec les
2 variantes.
et f() / g() appellera bien Affine::() (ou ton code n'aurait jamais
fonctionné, diantre)

J'ai peut-être manqué de clarté.
Il plante quand je lui passe des temporaires en paramètre, dans
l'exemple complet et l'exemple minimal. Ce qui me semble normal.


non c'était clair et évidemment, j'ai plutôt l'impression que tu manques
de clarté sur ce que tu as testé.


Après, quand je l'appelle avec des non temporaires, genre
Affine a1(1);
Affine a2(2);
Min m(a1,a2);


c'est aussi pire, ton compilo peut choisir de détruire a1 et a2 dès le
retour du constructeur de m; tu continues ici à croire à un ref.
counting; finalement (cette merdouille de) C managé est peut être
parfois nécessaire.

Min m2( *(new Affine(3)), *(new Affine(4));


qui est incorrect (pour d'autres raisons) si ces instances ne sont
jamais détruites.

PS: Faut dire, j'exagère un peu de faire ça en C++. On m'avait plutot
conseillé Matlab, mais je faisais trop d'erreurs de syntaxe...


voire gnuplot ...

Sylvain.

Avatar
Sylvain
James Kanze wrote on 07/03/2007 17:32:

Avec des copies
const Fonction f;
je perds le polymorphisme (et sur ce coup, il se plaint que Fonction
a une méthode virtuelle pure).


le polymorphisme existe toujours mais en effet 'Fonction' est
virtuel pure.


En fait, si Fonction est une classe abstraite (avec des
fonctions virtuelles pûre), il ne pourrait même pas en faire une
telle définition. Il ne peut pas exister des objets de type
Fonction en-dehors de la construction ou de la destruction des
objets d'un type dérivé.


j'avais lu le point "si j'utilisais /Fonction f/ je perdrais le
polymorphisme" ...
je tenais pour évident que la définition étant invalide ce que l'on
pourrait en dire avait peu de sens; pour autant le langage - à qui
appartient cette notion de polymorphisme - n'en était pas modifié.

Dans de tels cas, ça m'arrive souvent de faire que le paramètre
et la variable membre sont des std::auto_ptr. Quand je fais
vite, et que je n'ai pas le collecteur de Boehm sous la main,
évidemment. Si j'ai Boost installé, je pourrais aussi préférer
boost::shared_ptr dans ce cas précis. (Ce n'est pas une réponse
universelle à l'absence du GC, mais dans le cas des agents
polymorphe, il est tout à fait indiqué.)


pourquoi pas, je n'ai jamais utilisé ces auto ou shared ptr, ni jamais
regretter l'absence d'un GC, mais pour qui ne veut/sait pas gérer
correctement un pointeur, c'est sûrement bien.

Lors de l'exécution du code du destructeur de base, il faut bien
que la table de résolution virtuelle soit celle du type de base.
La norme ne dit rien sur l'état de l'objet une fois qu'on a
sorti du destructeurs -- une implémentation de déboggage
pourrait zapper l'objet avec des 0, ou 0xdeadbeef, par exemple.
Le compilateur a le droit de supposer qu'il n'y a pas de tels
accès, et s'il peut déterminer que le comportement du code dans
le destructeur de base (y compris, évidemment, des fonctions
qu'il pourrait appeler) ne change pas si la table n'est pas
changée, il pourrait ne pas la changer, à titre d'optimisation.


oui.

m.f et m.g n'existe pas en tant qu'objet. L'accès en est un
comportement indéfini. Ce qu'il voit (et ce que tu décris),
c'est un artifact de la façon que le compilateur génère le code.
On ne peut pas y compter.


c'est mieux dit ainsi.

Sylvain.



Avatar
Marc Boyer
Le 07-03-2007, Sylvain a écrit :
Marc Boyer wrote on 07/03/2007 17:19:
class Min : public Fonction {
const Fonction f;
const Fonction g;
// J'utilise des valeurs, et non des références, puisque c'est le
// point de débat (cf
// )


le post d'ID "" (1ier
post avec code) contient:

class Min : public Fonction {
const Fonction& f;
const Fonction& g;



Oui, ça je sais. Puis Laurent m'a dit "Probleme ici. Il ne faut pas
utiliser des references mais des copies.", et c'est pour cela que j'ai
évoqué la perte du polymorphisme.

"" est vu par TB comme un
email pas une ref. news, je ne peux donc pas vérifier (pas le temps de
les ouvrir en source un à un).


Désolé. C'est quoi TB ? Il aime comment les ID de news ? Enfin,
Google sait retrouver un post à partir d'un ID si tu veux.

nonobstant

class Min : public Fonction {
const Fonction f;
const Fonction g;

ne peux pas (ne doit pas) compiler, puisqu'il impose la construction de
classes virtuelles pures;


Sauf que dans l'ECM que je viens de poster, c'est
struct Fonction {
virtual double operator()(double x) const { assert(false); return 0;};
// La fonction virtuelle pure remplacee par une fct reelle pour
// que ça compile
virtual ~Fonction(){};
}

parlant de ce qui est imho non compilable, j'ai du mal à conjecturer...
soit tu amalgames différents essais, soit ton compilo est surprenant!


Oui, comme on tourne autour d'une solution, il y a diverses versions,
mais le post auquel tu viens de repondre possède un ECM ou Fonction
n'est pas virtuelle pure.

Après, quand je l'appelle avec des non temporaires, genre
Affine a1(1);
Affine a2(2);
Min m(a1,a2);


c'est aussi pire, ton compilo peut choisir de détruire a1 et a2 dès le
retour du constructeur de m; tu continues ici à croire à un ref.
counting; finalement (cette merdouille de) C managé est peut être
parfois nécessaire.


Tient, il garantie pas de n'appeller les destructeur qu'à la fin
du bloc courant ?

Min m2( *(new Affine(3)), *(new Affine(4));


qui est incorrect (pour d'autres raisons) si ces instances ne sont
jamais détruites.


Quel est le risque ?

PS: Faut dire, j'exagère un peu de faire ça en C++. On m'avait plutot
conseillé Matlab, mais je faisais trop d'erreurs de syntaxe...


voire gnuplot ...


Oui, C++ / gnuplot / make. Ca marche plutot bien.


Avatar
Sylvain
Marc Boyer wrote on 07/03/2007 20:46:

Oui, ça je sais. Puis Laurent m'a dit "Probleme ici. Il ne faut pas
utiliser des references mais des copies.", et c'est pour cela que j'ai
évoqué la perte du polymorphisme.


aurait-il du préciser "copie statique pouvant réellement exister" ??

utiliser des réf., des pointeurs ou des statiques ne change rien au
polymorphisme des instances et n'a en fait rien à voir avec lui !!

ayant:
struct A {
virtual foo() {}
};
struct B : A {
};
struct C : B {
virtual foo() {}
};

A a;
B b;
C c;

A& ra = a; // ou ra = b; ou ra = c;
B& rb = b; // ou rb = c;
C& rc = c;

A* pa = &a; // ou pa = &b; ou pa = &c;
B* pb = &b; // ou pb = &c;
C* pc = &c;

tous les appels:

a.foo(); b.foo(); c.foo();
ra.foo(); rb.foo(); rc.foo();
pa->foo(); pb->foo(); pc->foo();

sont résolus virtuellement et tiennent compte du polymorphisme.

tu sembles faire une distinction comme:

void bar(A& a) { a.foo(); }
bar(c); // ohhh du polymorphisme

vs

B b;
b.foo(); // ne serait pas polymorphe

une telle définition serait un non sens - et à ce titre on parle de
polymorphisme plus volontiers pour des types containers construits sur
le type de base de la famille de classes polymorphes (Cf "ma belle
listée chainée polymorphique", parutions diverses, 1988-1990).

Désolé. C'est quoi TB ?


thunberbird, comme indiqué dans le champ "user-agent".

Google sait retrouver un post à partir d'un ID si tu veux.


s'il a été indéxé, tu sais ce n'est pas tout à fait immédiat.

Sauf que dans l'ECM que je viens de poster, c'est
struct Fonction {
virtual double operator()(double x) const { assert(false); return 0;};
// La fonction virtuelle pure remplacee par une fct reelle pour
// que ça compile


peut être cela commençait à être un peu trop noyé de "et si" pour que je
note cette variation ...

le proto de () (pour ne parler que de lui) serait également non élégant,
ici Fonction /est/ virtuelle pure par essence; dans de rares cas, où on
peut être contraint à dévirtualiser une méthode, on le regrette souvent
car le compilo n'indiquera plus les méthodes que l'on a oublié de définir.

Oui, comme on tourne autour d'une solution, il y a diverses versions,
mais le post auquel tu viens de repondre possède un ECM ou Fonction
n'est pas virtuelle pure.


cette variante n'est pas une solution, seulement une machine à produire
des assert de manière certaine (cf "dans un "Fonction" on ne stocke
qu'un "Fonction"); donc ok, je n'ai pas répondu précisément,
spécifiquement à ce post juste parce que je n'avais pas imaginé (et donc
pas voulu lire) un truc qui ne peut pas marcher.

Tient, il garantie pas de n'appeller les destructeur qu'à la fin
du bloc courant ?


pour autant que l'on puisse définir bloc courant, si; mais jouer ainsi
avec des définitions variantes est risqué - je préfère franchement
définir /son/ bloc courant à sa portée d'utilisation (et elle s'arrête
dans le constructeur).

ces instances jamais détruites.
Quel est le risque ?



aucun si tes codes ne tournent qu'une fois, mais c'est juste une très
mauvaise habitude / attitude.

Sylvain.


1 2 3 4