[débutant] Retourner "string" ou "string*" ?

Le
tsalm
Bonjour,

Je suis tombé sur un cas qui me parait étrange. Je vous le soumet histoire
de bénéficier de vos lumières.

Le code ci-dessous affiche une fenêtre contenant deux zones de texte.
Le 1er texte ("hello 1"), retourné dans un "string*", est bien affiché.
Par contre le texte "hello 2", retourné dans un "string", ne s'affiche pas.
Mais pourquoi ce comportement ? D'autant que l'affichage dans les "cout"
affiche bien mes deux textes dans la console.

Au cas où ça viendrait du compilateur : j'utilise Visual C++ 2008
(Express).

D'avance merci.
TSalm

/* ********* CODE *************** */

#include "stdafx.h"

#include <string>
#include <iostream>

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_box.H>
#include <FL/Fl_Scroll.H>

using namespace std;

string* getStr1()
{
return new string("hello 1");
}

string getStr2()
{
return string("hello 2");
}

int _tmain(int argc, _TCHAR* argv[])
{
Fl_Window *win = new Fl_Window(200,200) ;
Fl_Box *lbl1 = new Fl_Box( 20,20,100,20 );
Fl_Box *lbl2 = new Fl_Box( 20,40,100,20 );

/* Insert into win */
win ->add( lbl1 ) ;
win ->add( lbl2 ) ;

/* set lbl text */
lbl1->label( getStr1()->c_str() );
lbl2->label( getStr2().c_str() );

/* for debugging purpose */
cout << "getStr1 = " << getStr1()->c_str() << "" << endl;
cout << "getStr2 = " << getStr2().c_str() << "" << endl;

/* Show window */
win ->show();
return(Fl::run());
}
/* ********* END CODE *********** */
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Michael DOUBEZ
Le #19856811
tsalm a écrit :
Bonjour,

Je suis tombé sur un cas qui me parait étrange. Je vous le soumet histoire
de bénéficier de vos lumières.

Le code ci-dessous affiche une fenêtre contenant deux zones de texte.
Le 1er texte ("hello 1"), retourné dans un "string*", est bien affiché.
Par contre le texte "hello 2", retourné dans un "string", ne s'affiche pas.
Mais pourquoi ce comportement ? D'autant que l'affichage dans les "cout"
affiche bien mes deux textes dans la console.

Au cas où ça viendrait du compilateur : j'utilise Visual C++ 2008
(Express).

D'avance merci.
TSalm

/* ********* CODE *************** */

#include "stdafx.h"

#include <string>
#include <iostream>

#include #include #include #include
using namespace std;

string* getStr1()
{
return new string("hello 1");
}

string getStr2()
{
return string("hello 2");
}

int _tmain(int argc, _TCHAR* argv[])
{
Fl_Window *win = new Fl_Window(200,200) ;
Fl_Box *lbl1 = new Fl_Box( 20,20,100,20 );
Fl_Box *lbl2 = new Fl_Box( 20,40,100,20 );

/* Insert into win */
win ->add( lbl1 ) ;
win ->add( lbl2 ) ;

/* set lbl text */
lbl1->label( getStr1()->c_str() );
lbl2->label( getStr2().c_str() );

/* for debugging purpose */
cout << "getStr1 = " << getStr1()->c_str() << "n" << endl;
cout << "getStr2 = " << getStr2().c_str() << "n" << endl;

/* Show window */
win ->show();
return(Fl::run());
}
/* ********* END CODE *********** */



Je ne connais pas Fl_* mais la doc de Fl_widget spécifie:
Sets the current label pointer.
<quote>
The label is shown somewhere on or next to the widget. The passed
pointer is stored unchanged in the widget (the string is not copied), so
if you need to set the label to a formatted value, make sure the buffer
is static, global, or allocated. The copy_label() method can be used to
make a copy of the label string automatically.
</quote>

Dans le cas de lbl2, tu passe un pointeur vers une temporaire donc c'est
normal que ça marche pas. Il s'affiche dans cout parcequ'il est utilisé
immédiatement.

Note: dans ton utilisation de getStr1(), tu as un leak à chaque appel.

--
Michael
tsalm
Le #19857241
Le Thu, 30 Jul 2009 21:53:07 +0200, Michael DOUBEZ

tsalm a écrit :
Bonjour,
Je suis tombé sur un cas qui me parait étrange. Je vous le soumet
histoire
de bénéficier de vos lumières.
Le code ci-dessous affiche une fenêtre contenant deux zones de texte.
Le 1er texte ("hello 1"), retourné dans un "string*", est bien affiché.
Par contre le texte "hello 2", retourné dans un "string", ne s'affiche
pas.
Mais pourquoi ce comportement ? D'autant que l'affichage dans les "cout"
affiche bien mes deux textes dans la console.
Au cas où ça viendrait du compilateur : j'utilise Visual C++ 2008
(Express).
D'avance merci.
TSalm
/* ********* CODE *************** */
#include "stdafx.h"
#include <string>
#include <iostream>
#include #include #include #include using namespace std;
string* getStr1()
{
return new string("hello 1");
}
string getStr2()
{
return string("hello 2");
}
int _tmain(int argc, _TCHAR* argv[])
{
Fl_Window *win = new Fl_Window(200,200) ;
Fl_Box *lbl1 = new Fl_Box( 20,20,100,20 );
Fl_Box *lbl2 = new Fl_Box( 20,40,100,20 );
/* Insert into win */
win ->add( lbl1 ) ;
win ->add( lbl2 ) ;
/* set lbl text */
lbl1->label( getStr1()->c_str() ); lbl2->label(
getStr2().c_str() );
/* for debugging purpose */
cout << "getStr1 = " << getStr1()->c_str() << "n" << endl;
cout << "getStr2 = " << getStr2().c_str() << "n" << endl;
/* Show window */
win ->show();
return(Fl::run());
}
/* ********* END CODE *********** */



Je ne connais pas Fl_* mais la doc de Fl_widget spécifie:
Sets the current label pointer.
<quote>
The label is shown somewhere on or next to the widget. The passed
pointer is stored unchanged in the widget (the string is not copied), so
if you need to set the label to a formatted value, make sure the buffer
is static, global, or allocated. The copy_label() method can be used to
make a copy of the label string automatically.
</quote>

Dans le cas de lbl2, tu passe un pointeur vers une temporaire donc c'est
normal que ça marche pas. Il s'affiche dans cout parcequ'il est utilisé
immédiatement.



Ok, je comprends.
Dans le "cout", ma "string" est dans la portée, contrairement à ma fenêtre
qui tourne dans un autre Thread. Correct ?

Note: dans ton utilisation de getStr1(), tu as un leak à chaque appel.



Bon, je ne voulais pas gacher la clarté de mon exemple :-) mais merci pour
cette remarque.
Michael Doubez
Le #19858181
On 30 juil, 22:55, tsalm
Le Thu, 30 Jul 2009 21:53:07 +0200, Michael DOUBEZ  
> tsalm a écrit :
>> Bonjour,
>>  Je suis tombé sur un cas qui me parait étrange. Je vous le soum et  
>> histoire
>> de bénéficier de vos lumières.
>>  Le code ci-dessous affiche une fenêtre contenant deux zones de te xte.
>> Le 1er texte ("hello 1"), retourné dans un "string*", est bien affic hé.
>> Par contre le texte "hello 2", retourné dans un "string", ne s'affic he  
>> pas.


[snip]
>>  using namespace std;
>>  string* getStr1()
>> {
>>     return new string("hello 1");
>> }
>>  string getStr2()
>> {
>>     return string("hello 2");
>> }
>>  int _tmain(int argc, _TCHAR* argv[])
>> {
>>     Fl_Window    *win    = new Fl_Window(200,200)    ;
>>     Fl_Box        *lbl1    = new Fl_Box( 20,20,100,2 0 );
>>     Fl_Box        *lbl2    = new Fl_Box( 20,40,100,2 0 );
>>      /* Insert into win    */
>>     win    ->add( lbl1 )    ;
>>     win    ->add( lbl2 )    ;
>>      /* set lbl text */
>>     lbl1->label( getStr1()->c_str() );       lbl2->label(  
>> getStr2().c_str()    );
>>      /* for debugging purpose */
>>     cout << "getStr1 = " << getStr1()->c_str() << "n" << endl;
>>     cout << "getStr2 = " << getStr2().c_str()  << "n" << endl ;
>>      /* Show window */
>>     win    ->show();
>>      return(Fl::run());



[snip]

> Dans le cas de lbl2, tu passe un pointeur vers une temporaire donc c'es t  
> normal que ça marche pas. Il s'affiche dans cout parcequ'il est utili sé  
> immédiatement.

Ok, je comprends.
Dans le "cout", ma "string" est dans la portée, contrairement à ma fe nêtre  
qui tourne dans un autre Thread. Correct ?



Non, ça n'a rien a voir avec les thread mais avec les durées de vie
des variable: getStr1() te renvoie une variable avec une durée de
stockage dynamique (utilisation de new) alors que getStr2() te renvoie
une variable avec une durée de stockage automatique (en pratique sur
le stack).

Dans le cas de la variable avec une durée de stockage automatique,
elle est détruire à la sortie du point de séquence:
- l'appel à lbl2->label( param ): dès après cette ligne, lbl1
référence un pointeur vers une zone mémoire qui a été détruite
(dépilée de la stack).
- l'appel à operator(ostream& /* cout */,const char*): après cet
appel la variable est détruite mais a déjà été utilisée pour
l'affichage.

Avec une durée de stockage static, ton code marcherait même avec les
threads:

string& getStr2()
{
static string hello="hello 2";
return hello;
}

--
Michael
James Kanze
Le #19858351
On Jul 31, 8:51 am, Michael Doubez
On 30 juil, 22:55, tsalm


Juste deux petits détails, pour être plus précis (mais c'est le
genre de chose que tsalm aura à apprendre tôt ou tard).

> Le Thu, 30 Jul 2009 21:53:07 +0200, Michael DOUBEZ
> > > tsalm a écrit :
> >> Bonjour,
> >> Je suis tombé sur un cas qui me parait étrange. Je vous le soum et
> >> histoire
> >> de bénéficier de vos lumières.
> >> Le code ci-dessous affiche une fenêtre contenant deux zones de te xte.
> >> Le 1er texte ("hello 1"), retourné dans un "string*", est bien aff iché.
> >> Par contre le texte "hello 2", retourné dans un "string", ne s'aff iche
> >> pas.
[snip]
> >> using namespace std;
> >> string* getStr1()
> >> {
> >> return new string("hello 1");
> >> }
> >> string getStr2()
> >> {
> >> return string("hello 2");
> >> }
> >> int _tmain(int argc, _TCHAR* argv[])
> >> {
> >> Fl_Window *win = new Fl_Window(200,200) ;
> >> Fl_Box *lbl1 = new Fl_Box( 20,20,100,20 );
> >> Fl_Box *lbl2 = new Fl_Box( 20,40,100,20 );
> >> /* Insert into win */
> >> win ->add( lbl1 ) ;
> >> win ->add( lbl2 ) ;
> >> /* set lbl text */
> >> lbl1->label( getStr1()->c_str() ); lbl2->label(
> >> getStr2().c_str() );
> >> /* for debugging purpose */
> >> cout << "getStr1 = " << getStr1()->c_str() << "n" << endl;
> >> cout << "getStr2 = " << getStr2().c_str() << "n" << endl;
> >> /* Show window */
> >> win ->show();
> >> return(Fl::run());



[snip]



> > Dans le cas de lbl2, tu passe un pointeur vers une
> > temporaire donc c'est normal que ça marche pas. Il
> > s'affiche dans cout parcequ'il est utilisé immédiatement.



> Ok, je comprends.
> Dans le "cout", ma "string" est dans la portée,
> contrairement à ma fenêtre qui tourne dans un autre Thread.
> Correct ?



Non, ça n'a rien a voir avec les thread mais avec les durées
de vie des variable: getStr1() te renvoie une variable avec
une durée de stockage dynamique (utilisation de new) alors que
getStr2() te renvoie une variable avec une durée de stockage
automatique (en pratique sur le stack).



Strictement parlant, c'est faux dans les deux cas. Une fonction
qui ne renvoie pas une référence renvoie toujours un objet
temporaire. Ce n'est pas une variable (puisqu'une variable a un
nom), et sa durée de vie est celle des objets temporaires, c-à-d
jusqu'à la fin de l'expression complète (ici, jusqu'au ;). Dans
le cas de getStr1, cet objet a un type pointeur, ce qui a la
durée de vie dynamique (c-à-d jusqu'il soit explicitement
détruit par le programmeur), c'est l'objet auquel le pointeur
pointe. (Mais vue que c'est cet objet qu'utilise le programme,
ça marche. Et pour être complet : un objet qui a une durée de
vie automatique vie jusqu'à la fin du bloc où il a été créé. Et
de tels objets sont tous des variables, avec un nom.)

Dans le cas de la variable avec une durée de stockage
automatique, elle est détruire à la sortie du point de
séquence:



Non. Les objets à durée de stockage automatique sont détruits
à la sortie du bloc, et les objets temporaires à la fin de
l'expression complète (ou plus tard dans certains cas précis).
Les points de séquence n'a rien à voir là-dedans (sauf dans la
mesure où la fin d'une expression complète est un point de
séquence).

- l'appel à lbl2->label( param ): dès après cette ligne,
lbl1 référence un pointeur vers une zone mémoire qui a été
détruite (dépilée de la stack).



Laisse tomber la notion de « dépilée » ; dans la plupart des
implémentations, la pile ne serait nettoyée qu'en sortie de la
fonction. L'objet temporaire a cessé d'exister : le compilateur
peut utiliser sa mémore pour d'autre chose, et dans le cas d'un
objet avec destructeur, le destructeur sera appelé. (Dans le cas
ici, la raison concrète du problème, c'est que le destructeur de
std::string a libéré la mémoire pointée par le résultat de
c_str().)

- l'appel à operator(ostream& /* cout */,const char*):
après cet appel la variable est détruite mais a déjà été
utilisée pour l'affichage.



Avec une durée de stockage static, ton code marcherait même
avec les threads:



string& getStr2()
{
static string hello="hello 2";
return hello;
}



Certes, mais ce n'est pas une solution très générale.

En fait, je dirais qu'il s'agit ici d'une erreur dans la
conception de la classe qu'il utilise. La solution que
j'utiliserais (je crois -- il faudrait que je sache plus sur
Fl_Box pour en être sûr), c'est de dériver de Fl_Box, en
remplaçant la fonction label avec une qui prend une std::string
en entrée, qu'elle sauve dans une variable locale, et qui
appelle la fonction label dans la classe de base en appelant
c_str() sur sa variable membre. Quelque chose comme :

class MyBox : public Fl_Box
{
public:
// Tous les constructeurs...
// (puisque les constructeurs ne sont pas hérités)
void label( std::string const& newLabel )
{
myLabel = newLabel ;
Fl_Box::label( myLabel.c_str() ) ;
}
private:
std::string myLabel ;
} ;

--
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
tsalm
Le #19863391
Le Fri, 31 Jul 2009 09:35:29 +0200, James Kanze écrit:

On Jul 31, 8:51 am, Michael Doubez
On 30 juil, 22:55, tsalm


Juste deux petits détails, pour être plus précis (mais c'est le
genre de chose que tsalm aura à apprendre tôt ou tard).

> Le Thu, 30 Jul 2009 21:53:07 +0200, Michael DOUBEZ
> > > tsalm a écrit :
> >> Bonjour,
> >> Je suis tombé sur un cas qui me parait étrange. Je vous le soumet
> >> histoire
> >> de bénéficier de vos lumières.
> >> Le code ci-dessous affiche une fenêtre contenant deux zones de
texte.
> >> Le 1er texte ("hello 1"), retourné dans un "string*", est bien
affiché.
> >> Par contre le texte "hello 2", retourné dans un "string", ne
s'affiche
> >> pas.
[snip]
> >> using namespace std;
> >> string* getStr1()
> >> {
> >> return new string("hello 1");
> >> }
> >> string getStr2()
> >> {
> >> return string("hello 2");
> >> }
> >> int _tmain(int argc, _TCHAR* argv[])
> >> {
> >> Fl_Window *win = new Fl_Window(200,200) ;
> >> Fl_Box *lbl1 = new Fl_Box( 20,20,100,20 );
> >> Fl_Box *lbl2 = new Fl_Box( 20,40,100,20 );
> >> /* Insert into win */
> >> win ->add( lbl1 ) ;
> >> win ->add( lbl2 ) ;
> >> /* set lbl text */
> >> lbl1->label( getStr1()->c_str() ); lbl2->label(
> >> getStr2().c_str() );
> >> /* for debugging purpose */
> >> cout << "getStr1 = " << getStr1()->c_str() << "n" << endl;
> >> cout << "getStr2 = " << getStr2().c_str() << "n" << endl;
> >> /* Show window */
> >> win ->show();
> >> return(Fl::run());



[snip]



> > Dans le cas de lbl2, tu passe un pointeur vers une
> > temporaire donc c'est normal que ça marche pas. Il
> > s'affiche dans cout parcequ'il est utilisé immédiatement.



> Ok, je comprends.
> Dans le "cout", ma "string" est dans la portée,
> contrairement à ma fenêtre qui tourne dans un autre Thread.
> Correct ?



Non, ça n'a rien a voir avec les thread mais avec les durées
de vie des variable: getStr1() te renvoie une variable avec
une durée de stockage dynamique (utilisation de new) alors que
getStr2() te renvoie une variable avec une durée de stockage
automatique (en pratique sur le stack).



Strictement parlant, c'est faux dans les deux cas. Une fonction
qui ne renvoie pas une référence renvoie toujours un objet
temporaire. Ce n'est pas une variable (puisqu'une variable a un
nom), et sa durée de vie est celle des objets temporaires, c-à-d
jusqu'à la fin de l'expression complète (ici, jusqu'au ;). Dans
le cas de getStr1, cet objet a un type pointeur, ce qui a la
durée de vie dynamique (c-à-d jusqu'il soit explicitement
détruit par le programmeur), c'est l'objet auquel le pointeur
pointe. (Mais vue que c'est cet objet qu'utilise le programme,
ça marche. Et pour être complet : un objet qui a une durée de
vie automatique vie jusqu'à la fin du bloc où il a été créé. Et
de tels objets sont tous des variables, avec un nom.)

Dans le cas de la variable avec une durée de stockage
automatique, elle est détruire à la sortie du point de
séquence:



Non. Les objets à durée de stockage automatique sont détruits
à la sortie du bloc, et les objets temporaires à la fin de
l'expression complète (ou plus tard dans certains cas précis).
Les points de séquence n'a rien à voir là-dedans (sauf dans la
mesure où la fin d'une expression complète est un point de
séquence).

- l'appel à lbl2->label( param ): dès après cette ligne,
lbl1 référence un pointeur vers une zone mémoire qui a été
détruite (dépilée de la stack).



Laisse tomber la notion de « dépilée » ; dans la plupart des
implémentations, la pile ne serait nettoyée qu'en sortie de la
fonction. L'objet temporaire a cessé d'exister : le compilateur
peut utiliser sa mémore pour d'autre chose, et dans le cas d'un
objet avec destructeur, le destructeur sera appelé. (Dans le cas
ici, la raison concrète du problème, c'est que le destructeur de
std::string a libéré la mémoire pointée par le résultat de
c_str().)

- l'appel à operator(ostream& /* cout */,const char*):
après cet appel la variable est détruite mais a déjà été
utilisée pour l'affichage.



Avec une durée de stockage static, ton code marcherait même
avec les threads:



string& getStr2()
{
static string hello="hello 2";
return hello;
}



Certes, mais ce n'est pas une solution très générale.

En fait, je dirais qu'il s'agit ici d'une erreur dans la
conception de la classe qu'il utilise. La solution que
j'utiliserais (je crois -- il faudrait que je sache plus sur
Fl_Box pour en être sûr), c'est de dériver de Fl_Box, en
remplaçant la fonction label avec une qui prend une std::string
en entrée, qu'elle sauve dans une variable locale, et qui
appelle la fonction label dans la classe de base en appelant
c_str() sur sa variable membre. Quelque chose comme :

class MyBox : public Fl_Box
{
public:
// Tous les constructeurs...
// (puisque les constructeurs ne sont pas hérités)
void label( std::string const& newLabel )
{
myLabel = newLabel ;
Fl_Box::label( myLabel.c_str() ) ;
}
private:
std::string myLabel ;
} ;




Extrêmement intéressant !
Merci à vous deux.
Publicité
Poster une réponse
Anonyme