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

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

5 réponses
Avatar
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() << "\n" << endl;
cout << "getStr2 = " << getStr2().c_str() << "\n" << endl;

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

5 réponses

Avatar
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 <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() << "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
Avatar
tsalm
Le Thu, 30 Jul 2009 21:53:07 +0200, Michael DOUBEZ
a écrit:

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 <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() << "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.
Avatar
Michael Doubez
On 30 juil, 22:55, tsalm wrote:
Le Thu, 30 Jul 2009 21:53:07 +0200, Michael DOUBEZ  
a écrit:
> 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
Avatar
James Kanze
On Jul 31, 8:51 am, Michael Doubez wrote:
On 30 juil, 22:55, tsalm wrote:



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

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



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
> a écrit:
> > 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.