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

return ne renvoi pas de copie d'un objet

8 réponses
Avatar
pv
Bonjour

J'ai un probleme avec une fonction membre d'une classe "Classe1" qui
renvoie une variable d'un type que l'on appellera "Classe2". Voici
l'implémentation de la fonction

Classe2 Classe1::foo()
{
Classe2 Temp1(); //constructeur par defaut
//traitements sur Temp1 ...
return Temp1;
}

Dans le programme principal je l'appel avec:

Classe1 Temp2(); //constructeur par defaut
Classe2 Temp3=Temp2.foo();

Cela me provoque une erreur.

Or je me suis apercu que Temp3 et Temp1 pointe vers la meme adresse.
Cela voudrait donc dire que "return" ne fait pas une copie de Temp1
avant de le renvoyer, mais fait plutot une reference. Comme je fais
des allocations dynamiques de tableaux dans Classe2 le tableau a été
effacé par appel du destructeur de Classe2 en fin de fonction "foo",
et celui ci n'est plus disponible dans le programme principal.

Ayant fait un petit tour sur le groupe de discussion j'ai vu que cela
pouvait venir d'une optimisation du compilateur. (La variable
temporaire est crée dans l'espace précédent dans la pile des appels).
Comme je ne connait pas son s*nom sous mon compilateur (VS .NET) j'ai
desactivé toutes les optimisations. Mais de toutes facons elles sont
desactivées en mode DEBUG....

Actuellement je contourne le probleme en passant des arguments par
references avec une fonction void foo(Classe2& objetClasse2), mais
c'est moins pratique, et mon code est plus lourd car je dois créer au
préalable une variable avant d'appeler la fonction.

J'ai acheté (et pas encore tout lu) le dernier "Le langage C++" par
Stroustrup mais tout ce que j'ai trouvé p 164 sur "return" n'indique
rien de particulier.

Merci d'avance à ceux qui aurait une petite idée sur la question.

8 réponses

Avatar
.oO LGV Oo.
"rotophil" a écrit dans le message de
news:
Bonjour

J'ai un probleme avec une fonction membre d'une classe "Classe1" qui
renvoie une variable d'un type que l'on appellera "Classe2". Voici
l'implémentation de la fonction

Classe2 Classe1::foo()
{
Classe2 Temp1(); //constructeur par defaut
//traitements sur Temp1 ...
return Temp1;
}

Dans le programme principal je l'appel avec:

Classe1 Temp2(); //constructeur par defaut
Classe2 Temp3=Temp2.foo();

Cela me provoque une erreur.

Or je me suis apercu que Temp3 et Temp1 pointe vers la meme adresse.
Cela voudrait donc dire que "return" ne fait pas une copie de Temp1
avant de le renvoyer, mais fait plutot une reference. Comme je fais
des allocations dynamiques de tableaux dans Classe2 le tableau a été
effacé par appel du destructeur de Classe2 en fin de fonction "foo",
et celui ci n'est plus disponible dans le programme principal.

Ayant fait un petit tour sur le groupe de discussion j'ai vu que cela
pouvait venir d'une optimisation du compilateur. (La variable
temporaire est crée dans l'espace précédent dans la pile des appels).
Comme je ne connait pas son s*nom sous mon compilateur (VS .NET) j'ai
desactivé toutes les optimisations. Mais de toutes facons elles sont
desactivées en mode DEBUG....

Actuellement je contourne le probleme en passant des arguments par
references avec une fonction void foo(Classe2& objetClasse2), mais
c'est moins pratique, et mon code est plus lourd car je dois créer au
préalable une variable avant d'appeler la fonction.

J'ai acheté (et pas encore tout lu) le dernier "Le langage C++" par
Stroustrup mais tout ce que j'ai trouvé p 164 sur "return" n'indique
rien de particulier.

Merci d'avance à ceux qui aurait une petite idée sur la question.



tu dis faire des allocations dynamiques de tableaux ; as-tu redéfinis le
constructeur de copie de l'opérateur d'affectation pour dupliquer ces zones
mémoires (à supposer que tu aies des pointeurs en tant que données membres
de tes classes) ?

Avatar
Fabien LE LEZ
On 27 Oct 2003 05:16:03 -0800, (rotophil) wrote:

Classe2 Classe1::foo()
{
Classe2 Temp1(); //constructeur par defaut


Non, déclaration de fonction.
Pour créer un objet avec le constructeur par défaut il faut écrire

Classe2 temp1;

Classe1 Temp2(); //constructeur par defaut


Idem, encore une déclaration de fonction.

--
;-)

Avatar
pv
Fabien LE LEZ wrote in message news:...
On 27 Oct 2003 05:16:03 -0800, (rotophil) wrote:

Classe2 Classe1::foo()
{
Classe2 Temp1(); //constructeur par defaut


Non, déclaration de fonction.
Pour créer un objet avec le constructeur par défaut il faut écrire

Classe2 temp1;

Classe1 Temp2(); //constructeur par defaut


Idem, encore une déclaration de fonction.



Effectivement en voulant synthétiser mon problème j'ai fauté ... il ne
faut pas de parenthèses...merci de ta remarque


Avatar
kanze
(rotophil) wrote in message
news:...

J'ai un probleme avec une fonction membre d'une classe "Classe1" qui
renvoie une variable d'un type que l'on appellera "Classe2". Voici
l'implémentation de la fonction

Classe2 Classe1::foo()
{
Classe2 Temp1(); //constructeur par defaut


Pas du tout. Déclaration d'une fonction.

//traitements sur Temp1 ...
return Temp1;


Ce qui doit provoquer une erreur ici. L'utilisation du nom d'une
fonction sans les () renvoie l'adresse de la fonction. Et ça
m'étonnerait que tu as une conversion implicite de Classe2 (*)() en
Classe2.

}

Dans le programme principal je l'appel avec:

Classe1 Temp2(); //constructeur par defaut


Non plus. Déclaration d'une fonction.

Je suppose qu'en fait, ce n'est pas comme ça dans ton code réel.

Classe2 Temp3=Temp2.foo();

Cela me provoque une erreur.


De quel genre ?

Est-ce que Classe2 a un constructeur de copie accessible ? (Mais sinon,
ta fonction Classe1::foo n'aurait pas dû compiler non plus.)

Or je me suis apercu que Temp3 et Temp1 pointe vers la meme adresse.


C'est possible. La norme donne explicitement le droit au compilateur de
supprimer un certain nombre de temporaires. (Ça s'appelle la NRVO --
Named Return Value Optimization -- si tu veux en chercher des
renseignements sur le reseau.) Dans ce cas-ci, c'est une optimisation
assez courante (je crois) de faire construire Temp1 directement dans
Temp3.

Cela voudrait donc dire que "return" ne fait pas une copie de Temp1
avant de le renvoyer, mais fait plutot une reference.


Ça veut dire que tu ne peux jamais être sûr du nombre de temporaires, et
que le code qui dépend de ce nombre est défectueux.

Typiquement, quand il y a une valeur de retour de type classe,
l'appelant allouera la mémoire pour la valeur de retour, et en passera
l'adresse à la fonction en paramètre caché. Dans le retour, la fonction
construira la valeur de retour à l'adresse indiquée. Ici, le compilateur
a rémarqué 1) que la valeur de retour serait immediatement copié dans
Temp3, puis détruite -- il a donc passé l'adresse de Temp3 directement,
plutôt que de créer un temporaire ; et 2) qu'à la fin de foo, la
variable Temp3 sera copiée à l'adresse cachée, puis immédiatement
detruite -- il a donc construit Temp3 directement à l'adresse caché, et
supprimé la copie.

La norme permet explicitement ces optimisations, même dans le cas où le
constructeur de copie à des effets de bord. Elle les permet parce que
normalement, on s'attend que le constructeur de copie fasse une copie,
et pas d'autre chose. (Il peut contenir des instructions
d'instrumentation, donc, des effets de bord, mais la correction du
programme n'en dépend pas.)

Comme je fais des allocations dynamiques de tableaux dans Classe2 le
tableau a été effacé par appel du destructeur de Classe2 en fin de
fonction "foo", et celui ci n'est plus disponible dans le programme
principal.


En es-tu certain ? Si le compilateur fasse l'optimisation en question,
il supprime non seulement l'appel au constructeur de copie, mais aussi
l'appel au destructeur. Ce que tu décris serait plutôt le symptome de ce
qui se passe si tu n'as pas bien défini le constructeur de copie, et que
le compilateur ne fait pas cette optimisation.

Ce que je te propose, pour clarifier la question, c'est d'instrumenter
tous les constructeurs et le destructeur -- y ajouter des sorties vers
std::cerr, avec affichage du pointeur this. Puis, fait un tout petit
exemple qui se compile et qui montre le problème, et de le poster, avec
les sorties et/ou les messages d'erreur du compilateur.

Ayant fait un petit tour sur le groupe de discussion j'ai vu que cela
pouvait venir d'une optimisation du compilateur. (La variable
temporaire est crée dans l'espace précédent dans la pile des appels).
Comme je ne connait pas son s*nom sous mon compilateur (VS .NET) j'ai
desactivé toutes les optimisations. Mais de toutes facons elles sont
desactivées en mode DEBUG....


Je ne sais pas. Si j'écrivais un compilateur, j'implémenterais bien
cette optimisation dans tous les cas, quelque soit le niveau
d'optimisation précisée. Précisement parce qu'elle peut influer sur la
sémantique du programme, et qu'il n'y a rien de plus frustrant pour un
programmer que d'avoir l'optimisation changer la sémantique.

--
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
pv
".oO LGV Oo." wrote in message news:<bnj8q3$lud$...
"rotophil" a écrit dans le message de
news:
Bonjour

J'ai un probleme avec une fonction membre d'une classe "Classe1" qui
renvoie une variable d'un type que l'on appellera "Classe2". Voici
l'implémentation de la fonction

Classe2 Classe1::foo()
{
Classe2 Temp1(); //constructeur par defaut
//traitements sur Temp1 ...
return Temp1;
}

Dans le programme principal je l'appel avec:

Classe1 Temp2(); //constructeur par defaut
Classe2 Temp3=Temp2.foo();

Cela me provoque une erreur.

Or je me suis apercu que Temp3 et Temp1 pointe vers la meme adresse.
Cela voudrait donc dire que "return" ne fait pas une copie de Temp1
avant de le renvoyer, mais fait plutot une reference. Comme je fais
des allocations dynamiques de tableaux dans Classe2 le tableau a été
effacé par appel du destructeur de Classe2 en fin de fonction "foo",
et celui ci n'est plus disponible dans le programme principal.

Ayant fait un petit tour sur le groupe de discussion j'ai vu que cela
pouvait venir d'une optimisation du compilateur. (La variable
temporaire est crée dans l'espace précédent dans la pile des appels).
Comme je ne connait pas son s*nom sous mon compilateur (VS .NET) j'ai
desactivé toutes les optimisations. Mais de toutes facons elles sont
desactivées en mode DEBUG....

Actuellement je contourne le probleme en passant des arguments par
references avec une fonction void foo(Classe2& objetClasse2), mais
c'est moins pratique, et mon code est plus lourd car je dois créer au
préalable une variable avant d'appeler la fonction.

J'ai acheté (et pas encore tout lu) le dernier "Le langage C++" par
Stroustrup mais tout ce que j'ai trouvé p 164 sur "return" n'indique
rien de particulier.

Merci d'avance à ceux qui aurait une petite idée sur la question.



tu dis faire des allocations dynamiques de tableaux ; as-tu redéfinis le
constructeur de copie de l'opérateur d'affectation pour dupliquer ces zones
mémoires (à supposer que tu aies des pointeurs en tant que données membres
de tes classes) ?


Non je ne l'ai pas fait, je pensais un peu naïvement qu'il le faisait
lui même. Ca doit etre d'ailleurs pour ca que les éléments des
tableaux (float**) de Temp1 et Temp3 ont la même adresse.
Je vais donc me mettre à l'oeuvre et je vous tiens au courant.

Merci de votre aide.


Avatar
pv
".oO LGV Oo." wrote in message news:<bnj8q3$lud$...
"rotophil" a écrit dans le message de
news:
Bonjour

J'ai un probleme avec une fonction membre d'une classe "Classe1" qui
renvoie une variable d'un type que l'on appellera "Classe2". Voici
l'implémentation de la fonction

Classe2 Classe1::foo()
{
Classe2 Temp1(); //constructeur par defaut
//traitements sur Temp1 ...
return Temp1;
}

Dans le programme principal je l'appel avec:

Classe1 Temp2(); //constructeur par defaut
Classe2 Temp3=Temp2.foo();

Cela me provoque une erreur.

Or je me suis apercu que Temp3 et Temp1 pointe vers la meme adresse.
Cela voudrait donc dire que "return" ne fait pas une copie de Temp1
avant de le renvoyer, mais fait plutot une reference. Comme je fais
des allocations dynamiques de tableaux dans Classe2 le tableau a été
effacé par appel du destructeur de Classe2 en fin de fonction "foo",
et celui ci n'est plus disponible dans le programme principal.

Ayant fait un petit tour sur le groupe de discussion j'ai vu que cela
pouvait venir d'une optimisation du compilateur. (La variable
temporaire est crée dans l'espace précédent dans la pile des appels).
Comme je ne connait pas son s*nom sous mon compilateur (VS .NET) j'ai
desactivé toutes les optimisations. Mais de toutes facons elles sont
desactivées en mode DEBUG....

Actuellement je contourne le probleme en passant des arguments par
references avec une fonction void foo(Classe2& objetClasse2), mais
c'est moins pratique, et mon code est plus lourd car je dois créer au
préalable une variable avant d'appeler la fonction.

J'ai acheté (et pas encore tout lu) le dernier "Le langage C++" par
Stroustrup mais tout ce que j'ai trouvé p 164 sur "return" n'indique
rien de particulier.

Merci d'avance à ceux qui aurait une petite idée sur la question.



tu dis faire des allocations dynamiques de tableaux ; as-tu redéfinis le
constructeur de copie de l'opérateur d'affectation pour dupliquer ces zones
mémoires (à supposer que tu aies des pointeurs en tant que données membres
de tes classes) ?



Non je ne l'ai pas fait, je pensais un peu naïvement qu'il le faisait
lui même. Ca doit etre d'ailleurs pour ca que les éléments des
tableaux (float**) de Temp1 et Temp3 ont la même adresse.
Je vais donc me mettre à l'oeuvre et je vous tiens au courant.

Merci de votre aide.


Avatar
pv
Bonjour

Classe2 Temp1(); //constructeur par defaut


Pas du tout. Déclaration d'une fonction.

//traitements sur Temp1 ...
return Temp1;


Ce qui doit provoquer une erreur ici. L'utilisation du nom d'une
fonction sans les () renvoie l'adresse de la fonction. Et ça
m'étonnerait que tu as une conversion implicite de Classe2 (*)() en
Classe2.



Je suis désolé c'est une faute de mon message qui n'est pas présente
dans mon programme




Je suppose qu'en fait, ce n'est pas comme ça dans ton code réel.

Classe2 Temp3=Temp2.foo();

Cela me provoque une erreur.


De quel genre ?

Est-ce que Classe2 a un constructeur de copie accessible ? (Mais sinon,
ta fonction Classe1::foo n'aurait pas dû compiler non plus.)



Non et je crois bien (cf reponses précédentes) que c'est bien ca le
problème.*
D'ailleurs explicité dans "Le langage C++"(Stroupstrup-2003) p272.
Donc avant de mettre en oeuvre ce que tu m'expliques tres clairement
dans la suite de ta réponse je vais commencé par écrire les
constructeur et l'affectation par copie.

Merci


Avatar
pv
J'ai fait le test: c'était le constructeur de copie:

Classe2(const Classe2&)

ainsi l'affectation par copie:

Classe2& operator=(const Classe2&);

qu'il fallait que je créé en gérant explicitement les (dés)allocations
dynamiques dans ceux ci.

Merci du coup de main!