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 operador de asignación de movimiento es una función miembro no estática no plantilla con el nombre operator = que puede ser llamado con un argumento del mismo tipo de clase y copia el contenido del argumento, posiblemente mutando el argumento.

Contenidos

Sintaxis

Para la sintaxis formal del operador de asignación de movimiento, consulte declaración de función . La lista de sintaxis a continuación solo demuestra un subconjunto de todas las sintaxis válidas del operador de asignación de movimiento.

tipo-de-retorno operator=( lista-de-parámetros  ); (1)
tipo-de-retorno operator=( lista-de-parámetros  ) cuerpo-de-función (2)
tipo-de-retorno operator=( lista-de-parámetros-sin-valor-predeterminado  ) = default; (3)
tipo-de-retorno operator=( lista-de-parámetros  ) = delete; (4)
tipo-de-retorno nombre-de-clase  :: operator=( lista-de-parámetros  ) cuerpo-de-función (5)
tipo-de-retorno nombre-de-clase  :: operator=( lista-de-parámetros-sin-valor-predeterminado  ) = default; (6)
class-name - la clase cuyo operador de asignación de movimiento se está declarando, el tipo de clase se da como T en las descripciones siguientes
parameter-list - una lista de parámetros de solo un parámetro, que es de tipo T&& , const T && , volatile T && o const volatile T &&
parameter-list-no-default - una lista de parámetros de solo un parámetro, que es de tipo T&& , const T && , volatile T && o const volatile T && y no tiene un argumento predeterminado
function-body - el cuerpo de función del operador de asignación de movimiento
return-type - cualquier tipo, pero T& es preferido para ser consistente con los tipos scala

Explicación

1) Declaración de un operador de asignación de movimiento dentro de la definición de clase.
2-4) Definición de un operador de asignación de movimiento dentro de la definición de clase.
3) El operador de asignación de movimiento está explícitamente predeterminado.
4) El operador de asignación de movimiento está eliminado.
5,6) Definición de un operador de asignación de movimiento fuera de la definición de clase (la clase debe contener una declaración (1) ).
6) El operador de asignación de movimiento está explícitamente predeterminado.
struct X
{
    X& operator=(X&& other);    // operador de asignación de movimiento
//  X operator=(const X other); // Error: tipo de parámetro incorrecto
};
union Y
{
    // los operadores de asignación de movimiento pueden tener sintaxis no listadas arriba,
    // siempre que sigan la sintaxis general de declaración de funciones
    // y no violen las restricciones listadas anteriormente
    auto operator=(Y&& other) -> Y&;       // OK: tipo de retorno final
    Y& operator=(this Y&& self, Y& other); // OK: parámetro de objeto explícito
//  Y& operator=(Y&&, int num = 1);        // Error: tiene otros parámetros que no son de objeto
};

El operador de asignación de movimiento se llama cada vez que es seleccionado por la resolución de sobrecarga , por ejemplo, cuando un objeto aparece en el lado izquierdo de una expresión de asignación, donde el lado derecho es un rvalue del mismo tipo o de un tipo implícitamente convertible.

Los operadores de asignación de movimiento normalmente transfieren los recursos mantenidos por el argumento (por ejemplo, punteros a objetos asignados dinámicamente, descriptores de archivo, sockets TCP, handles de hilos, etc.), en lugar de hacer copias de ellos, y dejan el argumento en algún estado válido pero por lo demás indeterminado. Dado que la asignación de movimiento no cambia el tiempo de vida del argumento, el destructor normalmente será llamado en el argumento en un punto posterior. Por ejemplo, asignar por movimiento desde un std::string o desde un std::vector puede resultar en que el argumento quede vacío. Una asignación de movimiento está definida de manera menos, no más, restrictiva que la asignación ordinaria; donde la asignación ordinaria debe dejar dos copias de los datos al completarse, la asignación de movimiento debe dejar solo una.

Operador de asignación de movimiento declarado implícitamente

Si no se proporcionan operadores de asignación de movimiento definidos por el usuario para un tipo de clase, y todo lo siguiente es cierto:

entonces el compilador declarará un operador de asignación de movimiento como un inline public miembro de su clase con la firma T & T :: operator = ( T && ) .

Una clase puede tener múltiples operadores de asignación de movimiento, por ejemplo, tanto T & T :: operator = ( const T && ) como T & T :: operator = ( T && ) . Si existen algunos operadores de asignación de movimiento definidos por el usuario, el usuario aún puede forzar la generación del operador de asignación de movimiento declarado implícitamente con la palabra clave default .

El operador de asignación de movimiento declarado implícitamente tiene una especificación de excepciones como se describe en especificación dinámica de excepciones (hasta C++17) especificación noexcept (desde C++17) .

Debido a que siempre se declara algún operador de asignación (move o copy) para cualquier clase, el operador de asignación de la clase base siempre queda oculto. Si se utiliza una using-declaration para incorporar el operador de asignación desde la clase base, y su tipo de argumento podría ser el mismo que el tipo de argumento del operador de asignación implícito de la clase derivada, la using-declaration también queda oculta por la declaración implícita.

Operador de asignación de movimiento definido implícitamente

Si el operador de asignación de movimiento declarado implícitamente no está eliminado ni es trivial, se define (es decir, se genera y compila un cuerpo de función) por el compilador si es odr-used o needed for constant evaluation (since C++14) .

Para los tipos unión, el operador de asignación de movimiento definido implícitamente copia la representación del objeto (como por std::memmove ).

Para tipos de clase no unión, el operador de asignación de movimiento realiza una asignación de movimiento completa miembro a miembro de las bases directas del objeto y de los miembros no estáticos inmediatos, en su orden de declaración, utilizando asignación incorporada para los escalares, asignación de movimiento miembro a miembro para arreglos, y operador de asignación de movimiento para tipos de clase (llamado no virtualmente).

El operador de asignación de movimiento definido implícitamente para una clase T es constexpr si

  • T es un literal type , y
  • el operador de asignación seleccionado para mover cada subobjeto de clase base directa es una función constexpr, y
  • para cada miembro de datos no estático de T que es de tipo clase (o arreglo del mismo), el operador de asignación seleccionado para mover ese miembro es una función constexpr.
(desde C++14)
(hasta C++23)

El operador de asignación de movimiento definido implícitamente para una clase T es constexpr .

(desde C++23)

Al igual que con la asignación por copia, no está especificado si los subobjetos de clase base virtual que son accesibles a través de más de una ruta en el entramado de herencia, son asignados más de una vez por el operador de asignación de movimiento definido implícitamente:

struct V
{
    V& operator=(V&& other)
    {
        // esto puede llamarse una o dos veces
        // si se llama dos veces, 'other' es el subobjeto V recién movido
        return *this;
    }
};
struct A : virtual V {}; // operator= llama a V::operator=
struct B : virtual V {}; // operator= llama a V::operator=
struct C : B, A {};      // operator= llama a B::operator=, luego A::operator=
                         // pero solo pueden llamar a V::operator= una vez
int main()
{
    C c1, c2;
    c2 = std::move(c1);
}

Operador de asignación de movimiento eliminado

El operador de asignación de movimiento declarado implícitamente o predeterminado para la clase T se define como eliminado si se satisface alguna de las siguientes condiciones:

  • T tiene un miembro de datos no estático de un tipo no-clase calificado como const (o posiblemente un arreglo multidimensional del mismo).
  • T tiene un miembro de datos no estático de tipo referencia.
  • T tiene un subobjeto potencialmente construido de tipo clase M (o posiblemente un arreglo multidimensional del mismo) tal que la resolución de sobrecarga aplicada para encontrar el operador de asignación de movimiento de M
  • no resulta en un candidato utilizable, o
  • en el caso de que el subobjeto sea un variant member , selecciona una función no trivial.

Un operador de asignación de movimiento declarado implícitamente y eliminado es ignorado por la resolución de sobrecarga .

Operador de asignación de movimiento trivial

El operador de asignación de movimiento para la clase T es trivial si se cumple todo lo siguiente:

  • No es proporcionado por el usuario (es decir, está definido implícitamente o por defecto);
  • T no tiene funciones miembro virtuales;
  • T no tiene clases base virtuales;
  • el operador de asignación de movimiento seleccionado para cada base directa de T es trivial;
  • el operador de asignación de movimiento seleccionado para cada miembro de tipo clase (o arreglo de tipo clase) no estático de T es trivial.

Un operador de asignación de movimiento trivial realiza la misma acción que el operador de asignación de copia trivial, es decir, hace una copia de la representación del objeto como si fuera mediante std::memmove . Todos los tipos de datos compatibles con el lenguaje C son trivialmente asignables por movimiento.

Operador de asignación de movimiento elegible

Un operador de asignación de movimiento es elegible si no está eliminado.

(until C++20)

Un operador de asignación de movimiento es elegible si se cumplen todas las siguientes condiciones:

(since C++20)

La trivialidad de los operadores de asignación de movimiento elegibles determina si la clase es un trivially copyable type .

Notas

Si se proporcionan tanto el operador de asignación de copia como el de movimiento, la resolución de sobrecarga selecciona la asignación de movimiento si el argumento es un rvalue (ya sea un prvalue como un temporal sin nombre o un xvalue como el resultado de std::move ), y selecciona la asignación de copia si el argumento es un lvalue (objeto con nombre o una función/operador que devuelve una referencia a lvalue). Si solo se proporciona la asignación de copia, todas las categorías de argumentos la seleccionan (siempre que tome su argumento por valor o como referencia a const, ya que los rvalues pueden vincularse a referencias const), lo que convierte a la asignación de copia en el respaldo para la asignación de movimiento cuando esta no está disponible.

No está especificado si los subobjetos de clase base virtual que son accesibles a través de más de una ruta en el esquema de herencia, son asignados más de una vez por el operador de asignación de movimiento definido implícitamente (lo mismo aplica para asignación de copia ).

Consulte sobrecarga del operador de asignación para obtener detalles adicionales sobre el comportamiento esperado de un operador de asignación de movimiento definido por el usuario.

Ejemplo

#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 << "Trying to move-assign A from rvalue temporary\n";
    a1 = f(A()); // move-assignment from rvalue temporary
    std::cout << "Trying to move-assign A from xvalue\n";
    a2 = std::move(a1); // move-assignment from xvalue
    std::cout << "\nTrying to move-assign B\n";
    B b1, b2;
    std::cout << "Before move, b1.s = \"" << b1.s << "\"\n";
    b2 = std::move(b1); // calls implicit move assignment
    std::cout << "After move, b1.s = \"" << b1.s << "\"\n";
    std::cout << "\nTrying to move-assign C\n";
    C c1, c2;
    c2 = std::move(c1); // calls the copy assignment operator
    std::cout << "\nTrying to move-assign D\n";
    D d1, d2;
    d2 = std::move(d1);
}

Salida:

Trying to move-assign A from rvalue temporary
move assigned
Trying to move-assign A from xvalue
move assigned
Trying to move-assign B
Before move, b1.s = "test"
move assigned
After move, b1.s = ""
Trying to move-assign C
copy assigned
Trying to move-assign D
move assigned

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 1353 C++11 las condiciones donde los operadores de asignación de movimiento por defecto son
definidos como eliminados no consideraban tipos de arreglos multidimensionales
considerar estos tipos
CWG 1402 C++11 un operador de asignación de movimiento por defecto que
llamaría a un operador de asignación de copia no trivial era
eliminado; un operador de asignación de movimiento por defecto que
está eliminado aún participaba en la resolución de sobrecarga
permite llamar a dicho
operador de asignación
de copia; se hace ignorado
en la resolución de sobrecarga
CWG 1806 C++11 faltaba la especificación para un operador de asignación de movimiento por defecto
que involucra una clase base virtual
añadida
CWG 2094 C++11 un subobjeto volátil hacía que un operador de asignación de movimiento
por defecto fuera no trivial ( CWG issue 496 )
la trivialidad no se ve afectada
CWG 2180 C++11 un operador de asignación de movimiento por defecto para la clase T
no estaba definido como eliminado si T es abstracta y tiene
clases base virtuales directas no asignables por movimiento
el operador se define
como eliminado en este caso
CWG 2595 C++20 un operador de asignación de movimiento no era elegible si existe
otro operador de asignación de movimiento que está más
restringido pero no satisface sus restricciones asociadas
puede ser elegible en este caso
CWG 2690 C++11 el operador de asignación de movimiento definido implícitamente para
tipos unión no copiaba la representación del objeto
copian la representación
del objeto

Véase también