L'élision de copie a été autorisée dans un certain nombre de circonstances. Cependant, même si cela était autorisé, le code devait encore pouvoir fonctionner comme si la copie n'était pas éludée. À savoir, il devait y avoir une copie accessible et / ou un constructeur de déplacement.
Copie garantie élision redéfinit un certain nombre de concepts de C, de sorte que certaines circonstances où les copies / mouvements pourraient être éludée ne provoquez pas en fait une copie / déplacer du tout . Le compilateur n'élide pas une copie; la norme dit qu'aucune copie de ce genre ne pourrait jamais avoir lieu.
Considérez cette fonction:
T Func() {return T();}
Sous les règles d'élision de copie non garanties, cela créera un temporaire, puis passera de ce temporaire à la valeur de retour de la fonction. Cette opération de déplacement peut être élidée, mais T
doit toujours avoir un constructeur de déplacement accessible même s'il n'est jamais utilisé.
De même:
T t = Func();
Il s'agit de l'initialisation de la copie de t
. Cela copiera initialize t
avec la valeur de retour de Func
. Cependant, T
doit toujours avoir un constructeur de mouvement, même s'il ne sera pas appelé.
L'élision de copie garantie redéfinit la signification d'une expression prvalue . Avant C ++ 17, les prvalues sont des objets temporaires. En C ++ 17, une expression prvalue est simplement quelque chose qui peut matérialiser un temporaire, mais ce n'est pas encore un temporaire.
Si vous utilisez une prvalue pour initialiser un objet du type de la prvalue, aucun temporaire n'est matérialisé. Lorsque vous le faites return T();
, cela initialise la valeur de retour de la fonction via une prvalue. Puisque cette fonction retourne T
, aucun temporaire n'est créé; l'initialisation de la prvalue initialise simplement directement la valeur de retour.
La chose à comprendre est que, puisque la valeur de retour est une valeur pr, ce n'est pas encore un objet . C'est simplement un initialiseur pour un objet, tout comme l' T()
est.
Lorsque vous le faites T t = Func();
, la prvalue de la valeur de retour initialise directement l'objet t
; il n'y a pas d'étape «créer un temporaire et copier / déplacer». Puisque Func()
la valeur de retour de est une prvalue équivalente à T()
, t
est directement initialisée par T()
, exactement comme si vous l'aviez fait T t = T()
.
Si une prvalue est utilisée d'une autre manière, la prvalue matérialisera un objet temporaire, qui sera utilisé dans cette expression (ou annulé s'il n'y a pas d'expression). Donc, si vous const T &rt = Func();
le faisiez, la prvalue matérialiserait un temporaire (en utilisant T()
comme initialiseur), dont la référence serait stockée rt
, avec le truc habituel d'extension de durée de vie temporaire.
Une chose garantie que l'élision vous permet de faire est de renvoyer des objets immobiles. Par exemple, lock_guard
ne peut pas être copié ou déplacé, vous ne pouvez donc pas avoir une fonction qui le renvoie par valeur. Mais avec une élision de copie garantie, vous le pouvez.
L'élision garantie fonctionne également avec l'initialisation directe:
new T(FactoryFunction());
Si FactoryFunction
retourne T
par valeur, cette expression ne copiera pas la valeur de retour dans la mémoire allouée. Il allouera à la place de la mémoire et utilisera la mémoire allouée comme mémoire de valeur de retour pour l'appel de fonction directement.
Ainsi, les fonctions d'usine qui retournent par valeur peuvent directement initialiser la mémoire allouée au tas sans même le savoir. Tant que ceux-ci fonctionnent en interne , bien sûr, les règles d'élision de la copie garantie. Ils doivent renvoyer une prvalue de type T
.
Bien sûr, cela fonctionne aussi:
new auto(FactoryFunction());
Au cas où vous n'aimeriez pas écrire les noms de caractères.
Il est important de reconnaître que les garanties ci-dessus ne fonctionnent que pour les valeurs prvalues. Autrement dit, vous n'obtenez aucune garantie lors du retour d'une variable nommée :
T Func()
{
T t = ...;
...
return t;
}
Dans ce cas, t
doit toujours avoir un constructeur de copie / déplacement accessible. Oui, le compilateur peut choisir d'optimiser la copie / le déplacement. Mais le compilateur doit toujours vérifier l'existence d'un constructeur de copie / déplacement accessible.
Donc, rien ne change pour l'optimisation de la valeur de retour nommée (NRVO).