unique_ptr<T>
ne permet pas la construction de copie, mais prend en charge la sémantique de déplacement. Pourtant, je peux retourner un à unique_ptr<T>
partir d'une fonction et affecter la valeur retournée à une variable.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
Le code ci-dessus compile et fonctionne comme prévu. Alors, comment se fait-il que cette ligne 1
n'invoque pas le constructeur de copie et n'entraîne pas d'erreurs de compilation? Si je devais utiliser la ligne à la 2
place, cela aurait du sens (l'utilisation de la ligne 2
fonctionne également, mais nous ne sommes pas tenus de le faire).
Je sais que C ++ 0x autorise cette exception unique_ptr
car la valeur de retour est un objet temporaire qui sera détruit dès la sortie de la fonction, garantissant ainsi l'unicité du pointeur renvoyé. Je suis curieux de savoir comment cela est implémenté, est-il spécial dans le compilateur ou existe-t-il une autre clause dans la spécification de langage que cela exploite?
la source
unique_ptr
. Toute la question porte sur 1 et 2 étant deux façons différentes de réaliser la même chose.main
sortie de la fonction, mais pas au moment de lafoo
sortie.Réponses:
Oui, voir 12.8 §34 et §35:
Je voulais juste ajouter un point de plus que le retour par valeur devrait être le choix par défaut ici car une valeur nommée dans l'instruction de retour dans le pire des cas, c'est-à-dire sans élisions en C ++ 11, C ++ 14 et C ++ 17 est traitée comme valeur r. Ainsi, par exemple, la fonction suivante se compile avec le
-fno-elide-constructors
drapeauLorsque l'indicateur est défini sur la compilation, deux mouvements (1 et 2) se produisent dans cette fonction, puis un mouvement plus tard (3).
la source
foo()
est en effet également sur le point d'être détruit (s'il n'était affecté à rien), tout comme la valeur de retour dans la fonction, et qu'il est donc logique que C ++ utilise un constructeur de déplacement lors de l'exécutionunique_ptr<int> p = foo();
?std::unique_ptr
), il existe une règle spéciale pour traiter d'abord les objets comme des valeurs r. Je pense que cela correspond entièrement à ce que Nikola a répondu.Ceci n'est en aucun cas spécifique à
std::unique_ptr
, mais s'applique à toute classe mobile. C'est garanti par les règles de langue puisque vous retournez par valeur. Le compilateur essaie d'éliminer les copies, appelle un constructeur de déplacement s'il ne peut pas supprimer des copies, appelle un constructeur de copie s'il ne peut pas se déplacer et échoue à compiler s'il ne peut pas copier.Si vous aviez une fonction qui accepte
std::unique_ptr
comme argument, vous ne pourriez pas lui passer p. Vous devez invoquer explicitement le constructeur de déplacement, mais dans ce cas, vous ne devez pas utiliser la variable p après l'appel àbar()
.la source
p
ne soit pas temporaire, le résultat defoo()
ce qui est retourné est; c'est donc une valeur r et elle peut être déplacée, ce qui rend l'affectationmain
possible. Je dirais que vous aviez tort, sauf que Nikola semble alors appliquer cette règle àp
lui-même qui EST par erreur.1
et Line2
? À mon avis, c'est la même chose car lors de la constructionp
dansmain
, il ne se soucie que du type de type de retourfoo
, non?unique_ptr n'a pas le constructeur de copie traditionnel. Au lieu de cela, il a un "constructeur de déplacement" qui utilise des références rvalue:
Une référence rvalue (la double esperluette) ne se liera qu'à une rvalue. C'est pourquoi vous obtenez une erreur lorsque vous essayez de passer une lvalue unique_ptr à une fonction. En revanche, une valeur renvoyée par une fonction est traitée comme une valeur r, de sorte que le constructeur de déplacement est appelé automatiquement.
Au fait, cela fonctionnera correctement:
Le temporaire unique_ptr ici est une rvalue.
la source
p
- "évidemment" une valeur l - être traité comme une valeur r dans la déclaration de retourreturn p;
dans la définition defoo
. Je ne pense pas qu'il y ait de problème avec le fait que la valeur de retour de la fonction elle-même puisse être "déplacée".Je pense que c'est parfaitement expliqué dans le point 25 du document Modern Modern C ++ de Scott Meyers . Voici un extrait:
Ici, RVO fait référence à l' optimisation de la valeur de retour , et si les conditions pour le RVO sont remplies, cela signifie renvoyer l'objet local déclaré à l'intérieur de la fonction que vous attendez de faire le RVO , ce qui est également bien expliqué dans le point 25 de son livre en se référant à la norme (ici, l' objet local inclut les objets temporaires créés par l'instruction return). La plus grande conséquence de l'extrait est que la suppression de la copie a lieu ou
std::move
est implicitement appliquée aux objets locaux renvoyés . Scott mentionne au point 25 questd::move
est implicitement appliqué lorsque le compilateur choisit de ne pas supprimer la copie et que le programmeur ne doit pas le faire explicitement.Dans votre cas, le code est clairement un candidat pour RVO car il renvoie l'objet local
p
et le type dep
est le même que le type de retour, ce qui entraîne la suppression de la copie. Et si le compilateur choisit de ne pas élider la copie, pour une raison quelconque, il sestd::move
serait mis en ligne1
.la source
Une chose que je n'ai pas vue dans les autres réponses estPour clarifier une autre réponse , il y a une différence entre retourner std :: unique_ptr qui a été créé dans une fonction et celui qui a été donné à cette fonction.L'exemple pourrait ressembler à ceci:
la source