Move assignment operator
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.
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
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:
- no hay copy constructors declarados por el usuario;
- no hay move constructors declarados por el usuario;
- no hay copy assignment operators declarados por el usuario;
- no hay destructor declarado por el usuario,
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
|
(desde C++14)
(hasta C++23) |
|
El operador de asignación de movimiento definido implícitamente para una clase
|
(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:
-
Ttiene un miembro de datos no estático de un tipo no-clase calificado como const (o posiblemente un arreglo multidimensional del mismo). -
Ttiene un miembro de datos no estático de tipo referencia. -
Ttiene un subobjeto potencialmente construido de tipo claseM(o posiblemente un arreglo multidimensional del mismo) tal que la resolución de sobrecarga aplicada para encontrar el operador de asignación de movimiento deM
-
- 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);
-
Tno tiene funciones miembro virtuales; -
Tno tiene clases base virtuales; -
el operador de asignación de movimiento seleccionado para cada base directa de
Tes trivial; -
el operador de asignación de movimiento seleccionado para cada miembro de tipo clase (o arreglo de tipo clase) no estático de
Tes 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 |