web-dev-qa-db-fra.com

Renvoyer une référence const à un objet au lieu d'une copie

En refacturant du code, je suis tombé sur des méthodes getter qui retournent un std :: string. Quelque chose comme ça par exemple:

class foo
{
private:
    std::string name_;
public:
    std::string name()
    {
        return name_;
    }
};

Le getter aurait sûrement intérêt à renvoyer un const std::string&? La méthode actuelle renvoie une copie moins efficace. Est-ce que retourner une référence const causerait des problèmes?

70
Rob

Cela ne peut poser problème que si l'appelant stocke la référence plutôt que de copier la chaîne et essaie de l'utiliser après la destruction de l'objet. Comme ça:

foo *pFoo = new foo;
const std::string &myName = pFoo->getName();
delete pFoo;
cout << myName;  // error! dangling reference

Toutefois, étant donné que votre fonction existante renvoie une copie, vous ne perdriez aucun des codes existants.

55
Dima

En fait, un autre problème spécifiquement avec le renvoi d'une chaîne not par référence est le fait que std::string fournit un accès via un pointeur à un const char* interne via la méthode c_str () . Cela m'a causé plusieurs heures de maux de tête de débogage. Par exemple, supposons que je veuille obtenir le nom de foo et le transmettre à JNI pour lui permettre de construire un jstring à transmettre ultérieurement à Java, et que name() renvoie une copie et non une référence. Je pourrais écrire quelque chose comme ça:

foo myFoo = getFoo(); // Get the foo from somewhere.
const char* fooCName = foo.name().c_str(); // Woops!  foo.name() creates a temporary that's destructed as soon as this line executes!
jniEnv->NewStringUTF(fooCName);  // No good, fooCName was released when the temporary was deleted.

Si votre appelant est sur le point de faire ce genre de choses, il peut être préférable d’utiliser un type de pointeur intelligent ou une référence const, ou tout au moins d’avoir un en-tête d’avertissement sur la méthode foo.name () Je mentionne JNI car les anciens codeurs Java pourraient être particulièrement vulnérables à ce type de chaînage de méthodes qui peut paraître inoffensif.

28
Ogre Psalm33

Un problème pour le retour de référence const serait que l'utilisateur codé quelque chose comme:

const std::string & str = myObject.getSomeString() ;

Avec un retour std::string, l'objet temporaire resterait en vie et attaché à str jusqu'à ce que str disparaisse de la portée.

Mais que se passe-t-il avec un const std::string &? Je suppose que nous aurions une référence const à un objet qui pourrait mourir si son objet parent le désalloue:

MyObject * myObject = new MyObject("My String") ;
const std::string & str = myObject->getSomeString() ;
delete myObject ;
// Use str... which references a destroyed object.

Donc, ma préférence va au retour de référence const (parce que, de toute façon, je suis plus à l'aise avec l'envoi d'une référence que d'espérer que le compilateur optimise le montant supplémentaire temporaire), tant que le contrat suivant est respecté: "si vous le voulez au-delà l'existence de mon objet, ils le copient avant la destruction de mon objet "

17
paercebal

Certaines implémentations de std :: string partagent la mémoire avec la sémantique de copie à l'écriture. Ainsi, le retour par valeur peut être presque aussi efficace que le retour par référence et vous n'avez pas à vous soucier des problèmes de durée de vie. (le runtime le fait pour vous). 

Si vous êtes inquiet à propos de la performance, alors comparez-le (<= ne pouvez pas le souligner autant) !!! Essayez les deux approches et mesurez le gain (ou l’absence de gain). Si on est meilleur et que vous vous souciez vraiment, utilisez-le. Si ce n'est pas le cas, préférez la valeur offerte par la protection offerte aux problèmes de vie mentionnés par d'autres personnes.

Vous savez ce qu'ils disent au sujet de faire des hypothèses ...

10
rlerallut

Ok, donc les différences entre le renvoi d’une copie et le renvoi de la référence sont:

  • Performance : Le renvoi de la référence peut être plus rapide ou pas; cela dépend de la façon dont std::string est implémenté par l'implémentation de votre compilateur (comme d'autres l'ont souligné). Mais même si vous renvoyez la référence, l'affectation après l'appel de la fonction implique généralement une copie, comme dans std::string name = obj.name();

  • Sécurité : Le renvoi de la référence peut poser ou non des problèmes (référence en suspens). Si les utilisateurs de votre fonction ne savent pas ce qu'ils font, stockant la référence en tant que référence et l'utilisant une fois que l'objet fournisseur est hors de portée, il y a un problème.

Si vous le voulez rapide et sûr utilisez boost :: shared_ptr . Votre objet peut stocker en interne la chaîne sous la forme shared_ptr et renvoyer un shared_ptr. De cette façon, il n'y aura pas de copie de l'objet en cours et il est toujours sécurisé (sauf si vos utilisateurs retirent le pointeur brut avec get() et le remplacent une fois que l'objet est hors de portée).

7
Frank

Je le changerais pour retourner const std :: string &. L’appelant fera probablement une copie du résultat de toute façon si vous ne modifiez pas tout le code de l’appel, mais cela ne posera aucun problème.

Si vous avez plusieurs threads appelant name (), il se produit une ride potentielle. Si vous retournez une référence, mais changez ensuite la valeur sous-jacente, la valeur de l'appelant changera. Mais le code existant n’a pas l’air d’être thread-safe de toute façon.

Jetez un coup d'œil à la réponse de Dima à un problème connexe, potentiellement improbable.

4
Kristopher Johnson

Il est concevable que vous puissiez casser quelque chose si l'appelant voulait vraiment une copie, car il était sur le point de modifier l'original et voulait en conserver une copie. Cependant, il est beaucoup plus probable qu’il retourne simplement une référence const.

La chose la plus simple à faire est de l'essayer et de le tester pour voir s'il fonctionne toujours, à condition que vous disposiez d'une sorte de test que vous puissiez exécuter. Sinon, je me concentrerais sur l'écriture du test avant de poursuivre la refactorisation.

3
Airsource Ltd

Est-ce que ça importe? Dès que vous utilisez un compilateur d'optimisation moderne, les fonctions qui retournent par valeur n'impliqueront pas de copie sauf si elles sont sémantiquement requises.

Voir le C++ lite FAQ à ce sujet.

2
christopher_f

Les chances sont assez bonnes que l'utilisation typique de cette fonction ne casse pas si vous passez à une référence const.

Si tout le code appelant cette fonction est sous votre contrôle, apportez simplement les modifications et vérifiez si le compilateur se plaint.

1
17 of 26

Renvoyer une référence à un membre expose l'implémentation de la classe ..__ Cela pourrait empêcher de changer de classe. Peut être utile pour les méthodes privées ou protégées si l’optimisation est nécessaire . Que devrait retourner un getter C++

0
Xavier Nguyen-Thai

Cela dépend de ce que vous devez faire. Peut-être voudrez-vous que tous les appelants modifient la valeur renvoyée sans changer de classe. Si vous retournez la référence const qui ne volera pas. 

Bien sûr, l’argument suivant est que l’appelant peut alors créer sa propre copie. Mais si vous savez comment la fonction sera utilisée et que cela se produit quand même, alors peut-être que cela vous évitera une étape ultérieure dans le code.

0
Joel Coehoorn

Je retourne normalement const & sauf si je ne peux pas. QBziZ donne un exemple de ce qui se passe. Bien sûr, QBziZ affirme également que std :: string possède une sémantique de copie sur écriture, ce qui est rarement vrai de nos jours, car COW implique beaucoup de temps système dans un environnement multithread. En renvoyant const &, vous obligez l'appelant à faire ce qu'il faut avec la chaîne à sa fin. Cependant, étant donné que vous utilisez du code déjà utilisé, vous ne devriez probablement pas le modifier, à moins que le profilage n’indique que la copie de cette chaîne pose d’énormes problèmes de performances. Ensuite, si vous décidez de le changer, vous devrez effectuer des tests approfondis pour vous assurer de ne rien casser. Espérons que les autres développeurs avec lesquels vous travaillez ne font pas des choses sommaires comme dans la réponse de Dima.

0
Brett Hall