Move assignment operator
Un opérateur d'affectation par déplacement est une fonction membre non-statique non-template avec le nom operator = qui peut être appelé avec un argument du même type de classe et copie le contenu de l'argument, en modifiant potentiellement l'argument.
Syntaxe
Pour la syntaxe formelle de l'opérateur de déplacement d'affectation, consultez déclaration de fonction . La liste de syntaxe ci-dessous ne présente qu'un sous-ensemble de toutes les syntaxes valides de l'opérateur de déplacement d'affectation.
type-de-retour
operator=(
liste-paramètres
);
|
(1) | ||||||||
type-de-retour
operator=(
liste-paramètres
)
corps-fonction
|
(2) | ||||||||
type-de-retour
operator=(
liste-paramètres-sans-défaut
) = default;
|
(3) | ||||||||
type-de-retour
operator=(
liste-paramètres
) = delete;
|
(4) | ||||||||
type-de-retour
nom-classe
::
operator=(
liste-paramètres
)
corps-fonction
|
(5) | ||||||||
type-de-retour
nom-classe
::
operator=(
liste-paramètres-sans-défaut
) = default;
|
(6) | ||||||||
| class-name | - |
la classe dont l'opérateur d'affectation par déplacement est déclaré, le type de classe est donné comme
T
dans les descriptions ci-dessous
|
| parameter-list | - |
une
liste de paramètres
d'un seul paramètre, qui est de type
T&&
,
const
T
&&
,
volatile
T
&&
ou
const
volatile
T
&&
|
| parameter-list-no-default | - |
une
liste de paramètres
d'un seul paramètre, qui est de type
T&&
,
const
T
&&
,
volatile
T
&&
ou
const
volatile
T
&&
et n'a pas d'argument par défaut
|
| function-body | - | le corps de fonction de l'opérateur d'affectation par déplacement |
| return-type | - |
tout type, mais
T&
est privilégié pour être cohérent avec les types scala
|
Explication
struct X { X& operator=(X&& other); // opérateur d'affectation de déplacement // X operator=(const X other); // Erreur : type de paramètre incorrect }; union Y { // les opérateurs d'affectation de déplacement peuvent avoir des syntaxes non listées ci-dessus, // tant qu'elles suivent la syntaxe générale de déclaration de fonction // et ne violent pas les restrictions énumérées ci-dessus auto operator=(Y&& other) -> Y&; // OK : type de retour trailing Y& operator=(this Y&& self, Y& other); // OK : paramètre objet explicite // Y& operator=(Y&&, int num = 1); // Erreur : possède d'autres paramètres non-objets };
L'opérateur d'affectation par déplacement est appelé chaque fois qu'il est sélectionné par la résolution de surcharge , par exemple lorsqu'un objet apparaît du côté gauche d'une expression d'affectation, où le côté droit est une rvalue du même type ou d'un type implicitement convertible.
Les opérateurs d'affectation par déplacement transfèrent généralement les ressources détenues par l'argument (par exemple, les pointeurs vers des objets alloués dynamiquement, les descripteurs de fichiers, les sockets TCP, les handles de thread, etc.), plutôt que d'en faire des copies, et laissent l'argument dans un état valide mais autrement indéterminé. Étant donné que l'affectation par déplacement ne change pas la durée de vie de l'argument, le destructeur sera généralement appelé sur l'argument ultérieurement. Par exemple, l'affectation par déplacement depuis un std::string ou depuis un std::vector peut entraîner que l'argument soit laissé vide. Une affectation par déplacement est définie de manière moins, et non plus, restrictive qu'une affectation ordinaire ; là où l'affectation ordinaire doit laisser deux copies des données à la fin, l'affectation par déplacement est tenue de n'en laisser qu'une.
Opérateur d'affectation de déplacement implicitement déclaré
Si aucun opérateur d'affectation de déplacement défini par l'utilisateur n'est fourni pour un type de classe, et que toutes les conditions suivantes sont vraies :
- il n'y a pas de copy constructors déclarés par l'utilisateur ;
- il n'y a pas de move constructors déclarés par l'utilisateur ;
- il n'y a pas de copy assignment operators déclarés par l'utilisateur ;
- il n'y a pas de destructor déclaré par l'utilisateur ,
alors le compilateur déclarera un opérateur d'affectation de déplacement comme inline public membre de sa classe avec la signature T & T :: operator = ( T && ) .
Une classe peut avoir plusieurs opérateurs d'affectation par déplacement, par exemple à la fois
T
&
T
::
operator
=
(
const
T
&&
)
et
T
&
T
::
operator
=
(
T
&&
)
. Si certains opérateurs d'affectation par déplacement définis par l'utilisateur sont présents, l'utilisateur peut toujours forcer la génération de l'opérateur d'affectation par déplacement déclaré implicitement avec le mot-clé
default
.
L'opérateur de déplacement déclaré implicitement possède une spécification d'exception comme décrit dans spécification d'exception dynamique (jusqu'à C++17) spécification noexcept (depuis C++17) .
Parce qu'un opérateur d'affectation (déplacement ou copie) est toujours déclaré pour toute classe, l'opérateur d'affectation de la classe de base est toujours masqué. Si une déclaration using est utilisée pour importer l'opérateur d'affectation de la classe de base, et que son type d'argument pourrait être le même que le type d'argument de l'opérateur d'affectation implicite de la classe dérivée, la déclaration using est également masquée par la déclaration implicite.
Opérateur d'affectation de déplacement implicitement défini
Si l'opérateur de déplacement déclaré implicitement n'est ni supprimé ni trivial, il est défini (c'est-à-dire qu'un corps de fonction est généré et compilé) par le compilateur s'il est odr-used ou nécessaire pour l'évaluation constante (depuis C++14) .
Pour les types union, l'opérateur de déplacement défini implicitement copie la représentation de l'objet (comme par std::memmove ).
Pour les types de classes non-union, l'opérateur d'affectation par déplacement effectue une affectation par déplacement complète membre à membre des bases directes et des membres non statiques immédiats de l'objet, dans leur ordre de déclaration, en utilisant l'affectation intégrée pour les scalaires, l'affectation par déplacement membre à membre pour les tableaux, et l'opérateur d'affectation par déplacement pour les types de classes (appelé non virtuellement).
|
L'opérateur de déplacement défini implicitement pour une classe
|
(depuis C++14)
(jusqu'à C++23) |
|
L'opérateur de déplacement défini implicitement pour une classe
|
(depuis C++23) |
Comme pour l'affectation par copie, il n'est pas spécifié si les sous-objets de classe de base virtuelle accessibles par plusieurs chemins dans le réseau d'héritage sont affectés plus d'une fois par l'opérateur d'affectation de déplacement défini implicitement :
struct V { V& operator=(V&& other) { // cela peut être appelé une ou deux fois // si appelé deux fois, 'other' est le sous-objet V venant d'être déplacé return *this; } }; struct A : virtual V {}; // operator= appelle V::operator= struct B : virtual V {}; // operator= appelle V::operator= struct C : B, A {}; // operator= appelle B::operator=, puis A::operator= // mais ils ne peuvent appeler V::operator= qu'une seule fois int main() { C c1, c2; c2 = std::move(c1); }
Opérateur d'affectation par déplacement supprimé
L'opérateur de déplacement implicitement déclaré ou par défaut pour la classe
T
est défini comme supprimé si l'une des conditions suivantes est satisfaite :
-
Tpossède un membre de données non statique d'un type non-classe qualifié const (ou éventuellement un tableau multidimensionnel de celui-ci). -
Tpossède un membre de données non statique de type référence. -
Tpossède un sous-objet potentiellement construit de type classeM(ou éventuellement un tableau multidimensionnel de celui-ci) tel que la résolution de surcharge appliquée pour trouver l'opérateur d'affectation par déplacement deM
-
- ne donne pas lieu à un candidat utilisable, ou
- dans le cas où le sous-objet est un variant member , sélectionne une fonction non triviale.
Un opérateur de déplacement implicitement déclaré comme supprimé est ignoré par la résolution de surcharge .
Opérateur de déplacement trivial
L'opérateur d'affectation par déplacement pour la classe
T
est trivial si toutes les conditions suivantes sont vraies :
- Il n'est pas fourni par l'utilisateur (c'est-à-dire qu'il est implicitement défini ou par défaut) ;
-
Tn'a pas de fonctions membres virtuelles ; -
Tn'a pas de classes de base virtuelles ; -
l'opérateur d'affectation par déplacement sélectionné pour chaque classe de base directe de
Test trivial ; -
l'opérateur d'affectation par déplacement sélectionné pour chaque membre de type classe (ou tableau de type classe) non statique de
Test trivial.
Un opérateur d'affectation de déplacement trivial effectue la même action que l'opérateur d'affectation de copie trivial, c'est-à-dire qu'il réalise une copie de la représentation de l'objet comme si par std::memmove . Tous les types de données compatibles avec le langage C sont trivialement affectables par déplacement.
Opérateur d'affectation par déplacement éligible
|
Un opérateur d'affectation par déplacement est éligible s'il n'est pas supprimé. |
(jusqu'en C++20) |
|
Un opérateur d'affectation par déplacement est éligible si toutes les conditions suivantes sont satisfaites :
|
(depuis C++20) |
La trivialité des opérateurs d'affectation par déplacement éligibles détermine si la classe est un type trivialement copiable .
Notes
Si les opérateurs d'assignation par copie et par déplacement sont tous deux fournis, la résolution de surcharge sélectionne l'assignation par déplacement si l'argument est une rvalue (soit une prvalue telle qu'un temporaire anonyme, soit une xvalue telle que le résultat de std::move ), et sélectionne l'assignation par copie si l'argument est une lvalue (objet nommé ou fonction/opérateur retournant une référence lvalue). Si seul l'opérateur d'assignation par copie est fourni, toutes les catégories d'arguments le sélectionnent (à condition qu'il prenne son argument par valeur ou par référence constante, car les rvalues peuvent se lier aux références constantes), ce qui fait de l'assignation par copie la solution de repli pour l'assignation par déplacement, lorsque le déplacement n'est pas disponible.
Il n'est pas spécifié si les sous-objets de classe de base virtuelle accessibles par plusieurs chemins dans le réseau d'héritage sont assignés plus d'une fois par l'opérateur de déplacement défini implicitement (s'applique également à l'assignation de copie ).
Voir la surcharge de l'opérateur d'assignation pour plus de détails sur le comportement attendu d'un opérateur de déplacement défini par l'utilisateur.
Exemple
#include <iostream> #include <string> #include <utility> struct A { std::string s; A() : s("test") {} A(const A& o) : s(o.s) { std::cout << "move failed!\n"; } A(A&& o) : s(std::move(o.s)) {} A& operator=(const A& other) { s = other.s; std::cout << "copy assigned\n"; return *this; } A& operator=(A&& other) { s = std::move(other.s); std::cout << "move assigned\n"; return *this; } }; A f(A a) { return a; } struct B : A { std::string s2; int n; // implicit move assignment operator B& B::operator=(B&&) // calls A's move assignment operator // calls s2's move assignment operator // and makes a bitwise copy of n }; struct C : B { ~C() {} // destructor prevents implicit move assignment }; struct D : B { D() {} ~D() {} // destructor would prevent implicit move assignment D& operator=(D&&) = default; // force a move assignment anyway }; int main() { A a1, a2; std::cout << "Tentative d'affectation de déplacement de A à partir d'un temporaire rvalue\n"; a1 = f(A()); // move-assignment from rvalue temporary std::cout << "Tentative d'affectation de déplacement de A à partir d'une xvalue\n"; a2 = std::move(a1); // move-assignment from xvalue std::cout << "\nTentative d'affectation de déplacement de B\n"; B b1, b2; std::cout << "Avant le déplacement, b1.s = \"" << b1.s << "\"\n"; b2 = std::move(b1); // calls implicit move assignment std::cout << "Après le déplacement, b1.s = \"" << b1.s << "\"\n"; std::cout << "\nTentative d'affectation de déplacement de C\n"; C c1, c2; c2 = std::move(c1); // calls the copy assignment operator std::cout << "\nTentative d'affectation de déplacement de D\n"; D d1, d2; d2 = std::move(d1); }
Sortie :
Tentative d'affectation de déplacement de A à partir d'un temporaire rvalue move assigned Tentative d'affectation de déplacement de A à partir d'une xvalue move assigned Tentative d'affectation de déplacement de B Avant le déplacement, b1.s = "test" move assigned Après le déplacement, b1.s = "" Tentative d'affectation de déplacement de C copy assigned Tentative d'affectation de déplacement de D move assigned
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 1353 | C++11 |
les conditions où les opérateurs d'affectation de déplacement par défaut sont
définis comme supprimés ne prenaient pas en compte les types de tableaux multidimensionnels |
prendre en compte ces types |
| CWG 1402 | C++11 |
un opérateur d'affectation de déplacement par défaut qui
appellerait un opérateur d'affectation de copie non trivial était supprimé ; un opérateur d'affectation de déplacement par défaut qui est supprimé participait toujours à la résolution de surcharge |
permet l'appel à un tel
opérateur d'affectation de copie ; rendu ignoré dans la résolution de surcharge |
| CWG 1806 | C++11 |
la spécification pour un opérateur d'affectation de déplacement par défaut
impliquant une classe de base virtuelle était manquante |
ajoutée |
| CWG 2094 | C++11 |
un sous-objet volatile rendait un opérateur d'affectation
de déplacement par défaut non trivial ( CWG issue 496 ) |
trivialité non affectée |
| CWG 2180 | C++11 |
un opérateur d'affectation de déplacement par défaut pour la classe
T
n'était pas défini comme supprimé si
T
est abstraite et possède
des classes de base virtuelles directes non affectables par déplacement |
l'opérateur est défini
comme supprimé dans ce cas |
| CWG 2595 | C++20 |
un opérateur d'affectation de déplacement n'était pas éligible s'il existe
un autre opérateur d'affectation de déplacement qui est plus contraint mais ne satisfait pas ses contraintes associées |
il peut être éligible dans ce cas |
| CWG 2690 | C++11 |
l'opérateur d'affectation de déplacement implicitement défini pour
les types union ne copiait pas la représentation d'objet |
ils copient la représentation
d'objet |