Namespaces
Variants

Move assignment operator

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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.

Table des matières

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

1) Déclaration d'un opérateur d'affectation par déplacement à l'intérieur de la définition de classe.
2-4) Définition d'un opérateur d'affectation de déplacement à l'intérieur de la définition de classe.
3) L'opérateur d'affectation par déplacement est explicitement défini par défaut.
4) L'opérateur d'affectation par déplacement est supprimé.
5,6) Définition d'un opérateur d'affectation de déplacement en dehors de la définition de classe (la classe doit contenir une déclaration (1) ).
6) L'opérateur d'affectation par déplacement est explicitement défini par défaut.
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 :

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 T est constexpr si

  • T est un literal type , et
  • l'opérateur d'affectation sélectionné pour déplacer chaque sous-objet de classe de base directe est une fonction constexpr, et
  • pour chaque membre de données non statique de T qui est de type classe (ou tableau de celui-ci), l'opérateur d'affectation sélectionné pour déplacer ce membre est une fonction constexpr.
(depuis C++14)
(jusqu'à C++23)

L'opérateur de déplacement défini implicitement pour une classe T est constexpr .

(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 :

  • T possède un membre de données non statique d'un type non-classe qualifié const (ou éventuellement un tableau multidimensionnel de celui-ci).
  • T possède un membre de données non statique de type référence.
  • T possède un sous-objet potentiellement construit de type classe M (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 de M
  • 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) ;
  • T n'a pas de fonctions membres virtuelles ;
  • T n'a pas de classes de base virtuelles ;
  • l'opérateur d'affectation par déplacement sélectionné pour chaque classe de base directe de T est 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 T est 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 :

  • Il n'est pas supprimé.
  • Ses contraintes associées (le cas échéant) sont satisfaites.
  • Aucun opérateur d'affectation par déplacement dont les contraintes associées sont satisfaites n'est plus contraint .
(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

Voir aussi