Copy elision
Lorsque certains critères sont remplis, la création d'un objet de classe à partir d'un objet source du même type (en ignorant les qualifications cv) peut être omise, même si le constructeur sélectionné et/ou le destructeur pour l'objet ont des effets secondaires. Cette élision de création d'objet est appelée copy elision .
Table des matières |
Explication
L'élision de copie est autorisée dans les circonstances suivantes (qui peuvent être combinées pour éliminer plusieurs copies) :
- Dans une return instruction d'une fonction avec un type de retour de classe, lorsque l'opérande est le nom d'un objet non volatile obj ayant une durée de stockage automatique (autre qu'un paramètre de fonction ou un paramètre de gestionnaire ), l' initialisation par copie de l'objet résultat peut être omise en construisant obj directement dans l'objet résultat de l'appel de fonction. Cette variante de l'élision de copie est connue sous le nom d' optimisation de la valeur de retour nommée (NRVO).
|
(jusqu'à C++17) |
|
(depuis C++11) |
|
(depuis C++20) |
Lorsque l'élimination de copie se produit, l'implémentation traite la source et la cible de l'initialisation omise comme simplement deux manières différentes de se référer au même objet.
|
La destruction se produit au moment le plus tardif entre les deux moments où les deux objets auraient été détruits sans l'optimisation. |
(until C++11) |
|
Si le premier paramètre du constructeur sélectionné est une référence à une valeur de déplacement (rvalue reference) du type de l'objet, la destruction de cet objet se produit lorsque la cible aurait été détruite. Sinon, la destruction se produit au moment le plus tardif entre les deux moments où les deux objets auraient été détruits sans l'optimisation. |
(since C++11) |
Sémantique des prvalues ("suppression de copie garantie")Depuis C++17, une prvalue n'est pas matérialisée avant d'être nécessaire, puis elle est construite directement dans le stockage de sa destination finale. Cela signifie parfois que même lorsque la syntaxe du langage suggère visuellement une copie/déplacement (par ex. l'initialisation par copie ), aucune copie/déplacement n'est effectué — ce qui signifie que le type n'a pas besoin de posséder un constructeur de copie/déplacement accessible. Les exemples incluent :
T f() { return U(); // constructs a temporary of type U, // then initializes the returned T from the temporary } T g() { return T(); // constructs the returned T directly; no move }
T x = T(T(f())); // x is initialized by the result of f() directly; no move
struct C { /* ... */ }; C f(); struct D; D g(); struct D : C { D() : C(f()) {} // no elision when initializing a base class subobject D(int) : D(g()) {} // no elision because the D object being initialized might // be a base-class subobject of some other class }; Remarque : Cette règle ne spécifie pas une optimisation, et la Norme ne la décrit pas formellement comme une "suppression de copie" (car rien n'est supprimé). Au lieu de cela, la spécification du langage cœur de C++17 concernant les prvalues et les temporaires est fondamentalement différente de celle des révisions antérieures de C++ : il n'y a plus de temporaire à partir duquel copier/déplacer. Une autre façon de décrire la mécanique de C++17 est le "passage de valeur non matérialisée" ou la "matérialisation temporaire différée" : les prvalues sont retournées et utilisées sans jamais matérialiser de temporaire. |
(depuis C++17) |
Notes
La copie élidée est la seule forme d'optimisation autorisée (jusqu'à C++14) l'une des deux formes d'optimisation autorisées, conjointement avec l'élision d'allocation et l'extension , (depuis C++14) qui peut modifier les effets secondaires observables. Comme certains compilateurs n'effectuent pas l'élision de copie dans toutes les situations où elle est autorisée (par exemple, en mode débogage), les programmes qui dépendent des effets secondaires des constructeurs de copie/déplacement et des destructeurs ne sont pas portables.
|
Dans une instruction return ou une expression throw , si le compilateur ne peut pas réaliser l'élision de copie mais que les conditions pour l'élision de copie sont remplies, ou le seraient sauf que la source est un paramètre de fonction, le compilateur tentera d'utiliser le constructeur de déplacement même si l'opérande source est désignée par un lvalue (jusqu'à C++23) l'opérande source sera traitée comme un rvalue (depuis C++23) ; voir l'instruction return pour plus de détails. Dans les expressions constantes et l' initialisation constante , l'élision de copie n'est jamais effectuée. struct A { void* p; constexpr A() : p(this) {} A(const A&); // Disable trivial copyability }; constexpr A a; // OK: a.p points to a constexpr A f() { A x; return x; } constexpr A b = f(); // error: b.p would be dangling and point to the x inside f constexpr A c = A(); // (until C++17) error: c.p would be dangling and point to a temporary // (since C++17) OK: c.p points to c; no temporary is involved |
(depuis C++11) |
| Macro de test de fonctionnalité | Valeur | Std | Fonctionnalité |
|---|---|---|---|
__cpp_guaranteed_copy_elision
|
201606L
|
(C++17) | Élimination de copie garantie via les catégories de valeur simplifiées |
Exemple
#include <iostream> struct Noisy { Noisy() { std::cout << "constructed at " << this << '\n'; } Noisy(const Noisy&) { std::cout << "copy-constructed\n"; } Noisy(Noisy&&) { std::cout << "move-constructed\n"; } ~Noisy() { std::cout << "destructed at " << this << '\n'; } }; Noisy f() { Noisy v = Noisy(); // (until C++17) copy elision initializing v from a temporary; // the move constructor may be called // (since C++17) "guaranteed copy elision" return v; // copy elision ("NRVO") from v to the result object; // the move constructor may be called } void g(Noisy arg) { std::cout << "&arg = " << &arg << '\n'; } int main() { Noisy v = f(); // (until C++17) copy elision initializing v from the result of f() // (since C++17) "guaranteed copy elision" std::cout << "&v = " << &v << '\n'; g(f()); // (until C++17) copy elision initializing arg from the result of f() // (since C++17) "guaranteed copy elision" }
Sortie possible :
constructed at 0x7fffd635fd4e &v = 0x7fffd635fd4e constructed at 0x7fffd635fd4f &arg = 0x7fffd635fd4f destructed at 0x7fffd635fd4f destructed at 0x7fffd635fd4e
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Appliqué à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 1967 | C++11 |
lors de l'élision de copie utilisant un constructeur de déplacement,
la durée de vie de l'objet déplacé était encore prise en compte |
non prise en compte |
| CWG 2426 | C++17 | le destructeur n'était pas requis lors du retour d'une prvalue | le destructeur est potentiellement invoqué |
| CWG 2930 | C++98 |
seules les opérations de copie(/déplacement) pouvaient être élidées, mais un
constructeur non-copie(/déplacement) peut être sélectionné par copie-initialization |
élide toute construction d'objet
des copy-initializations associées |