OVH Cloud OVH Cloud

Reference constante sur donnee membre d'un objet temporaire ?

13 réponses
Avatar
Aurélien REGAT-BARREL
Bonjour,
D'après ce que je sais, si une référence constante est initialisée avec un
objet temporaire, on a la garantie que l'objet temporaire persiste tant que
la référence existe. OK.
Question : et si c'est une donnée membre d'un objet temporaire :

#include <vector>
#include <iostream>

class Test
{
public:
Test()
{
vect.push_back( 1 );
vect.push_back( 2 );
vect.push_back( 3 );
}

const std::vector<int> & get_vector() const
{
return this->vect;
}
private:
std::vector<int> vect;
};

Test get_test()
{
return Test();
}

int main()
{
const std::vector<int> & v = get_test().get_vector();
std::cout << v.size() << '\n';
}

g++ m'affiche 3, VC++ 0. Qui a raison ?

--
Aurélien REGAT-BARREL

3 réponses

1 2
Avatar
Aurélien REGAT-BARREL
Ultimataupe wrote:
Bonjour,
D'après ce que je sais, si une référence constante est initialisée
avec un objet temporaire, on a la garantie que l'objet temporaire
persiste tant que la référence existe. OK.
J'aimerais bien savoir d'ou tu tiens ça.



Du Stroustrup (page 110 tout en haut).
De Andrei Alexandrescu dans son article sur les ScopeGuard
http://www.cuj.com/documents/s€00/cujcexp1812alexandr/alexandr.htm
"According to the C++ Standard, a reference initialized with a temporary
value makes that temporary value live for the lifetime of the reference
itself."

Ce genre de pratique est à bannir.
D'une manière générale il est fortement déconseillé de retourner une
référence ou un pointeur sur une donnée membre (ce qui est fait dans
get_vector) car on a plus aucune visibilité sur la durée de vie de
l'objet retourné.


AMHA, c'est plutot le problème du mélange renvoi par référence / copie qui
est à éviter. A savoir c'est get_test() et son retour par copie qui pose
problème ici. A la limite, j'irai même jusqu'à dire que c'est le retour par
copie qui est à bannir, car on a encore moins de visibilité sur la durée de
vie de la copie temporaire crée.
Renvoyer une référence, c'est quand même super courant dans les accesseurs.
Surtout pour des vecteur pour qui une recopie n'est pas négligeable.

--
Aurélien REGAT-BARREL


Avatar
Aurélien REGAT-BARREL
Est ce que le code suivant est valide dans ce cas :

const std::string& foo() { return "bar"; }
int main() { std::cout << foo(); }


Non parce que l'objet temporaire est dans la pile de foo(). C'est comme si
tu avais écrit:

const std::string& foo()
{
std::string bar( "bar" );
return bar;
}

De même, ça c'est pas bon:

const std::string & foo( std::string s )
{
return s;
}

Par contre ça c'est bon:

std::string foo()
{
return "bar";
}

int main()
{
const std::string & bar = foo();
std::cout << bar;
}

Et ça aussi:

const std::string & foo( const std::string & b )
{
return b;
}

int main()
{
std::string s( "bar" );
const std::string & bar = foo( s );
std::cout << bar;
}

Mais ça non:

const std::string & foo( const std::string & b )
{
return b;
}

int main()
{
const std::string & bar = foo( "bar" );
std::cout << bar;
}

parce que le temporaire n'est valide que pendant l'exécution de foo().

--
Aurélien REGAT-BARREL

Avatar
kanze
Ultimataupe wrote:
D'après ce que je sais, si une référence constante est
initialisée avec un objet temporaire, on a la garantie que
l'objet temporaire persiste tant que la référence
existe. OK.


J'aimerais bien savoir d'ou tu tiens ça.


Peut-être des paragraphes 4 et 5 de la section 12.2 de la
norme. Mais ce n'est rien de nouveau ; c'était déjà dans l'ARM,
il me semble. (L'ARM ne spécifiait pas la durée de vie des
temporaires dans le cas général, mais il me semble qu'il
précisait bien que dans ce cas-ci, ils avaient au moins la durée
de vie de la référence. Mais je n'ai pas ma copie sous la main
pour vérifier.)

Question : et si c'est une donnée membre d'un objet temporaire :

#include <vector>
#include <iostream>

class Test
{
public:
Test()
{
vect.push_back( 1 );
vect.push_back( 2 );
vect.push_back( 3 );
}

const std::vector<int> & get_vector() const
{
return this->vect;
}
private:
std::vector<int> vect;
};

Test get_test()
{
return Test();
}

int main()
{
const std::vector<int> & v = get_test().get_vector();
std::cout << v.size() << 'n';
}

g++ m'affiche 3, VC++ 0. Qui a raison ?


En rajoutant le destructeur de Test
~Test()
{
std::cout <<"~Test()" << 'n';
}

tu t'apercois que ton objet Test ( et donc le vector) est
detruit avant d'appeller v.size();


Il faudrait qu'il instrumente v.size() aussi pour en être
sûr. (En fait, dans un cas comme ceci, je remplacerai bien le
std::vector avec une classe triviale à moi, justement afin
pouvoir l'instrumenter.)

Donc v.size() est appellé sur un objet détruit.

Comme l'objet est stocké dans la pile, la zone mémoire n'est
pas écrasé (sauf si tu sors de la fonction et que tu rentre
dans un autre) c'est pur ça que l'appel à size() ne plante
pas.

Apparemment le destructeur de vector de VC++ efface bien le
tableau et remet sa taille à 0 ce qui n'est pas fait par g++.
On pourrait dire que VC++ est plus propre.


Je dirais plutôt que c'est un aléa de l'implémentation. Je ne
vois pas l'intérêt en général de mettre à zéro la mémoire qui
cesse d'exister (en ce qui concerne le programme).

Personne n'a raison ni tord . Rien n'indique dans les spec C++
ce qu'on doit mettre dans de la mémoire non utilisée.

Ce genre de pratique est à bannir.

D'une manière générale il est fortement déconseillé de
retourner une référence ou un pointeur sur une donnée membre
(ce qui est fait dans get_vector) car on a plus aucune
visibilité sur la durée de vie de l'objet retourné.


Tu veux dire comme ce qui est fait dans vector<>::operator[] ?

Ça peut poser des problèmes. Surtout dans les cas comme vector,
où la référence peut être invalidée sans que le vector cesse
d'exister. Il faut bien reconnaître le problème, et peser les
alternatifs. Un vector où la seule façon de modifier les
éléments, c'est une fonction put(), n'est pas l'idéal non plus.
Un vector qui utilise un proxy pour le retour de l'opérateur []
(et les iterateurs) a des avantages certains, mais un coût en
temps d'execution qui pourrait être genant dans certaines
applications.

L'importance, c'est de faire la décision en connaissance de
cause. Dans la mésure du possible, il est préférable d'éviter
une durée de vie inférieur à la durée de vie de l'objet
principal. Mais réalistiquement, ce n'est pas toujours possible,
et dans ces cas-là, on fait ce qu'on peut, et on documente
exactement ce qu'on garantit.

--
James Kanze GABI Software
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


1 2