Move constructors
Un constructor de movimiento es un constructor que puede ser invocado con un argumento del mismo tipo de clase y copia el contenido del argumento, posiblemente mutando el argumento.
Contenidos |
Sintaxis
class-name
(
parameter-list
);
|
(1) | ||||||||
class-name
(
parameter-list
)
function-body
|
(2) | ||||||||
class-name
(
single-parameter-list
) = default;
|
(3) | ||||||||
class-name
(
parameter-list
) = delete;
|
(4) | ||||||||
class-name
::
class-name
(
parameter-list
)
function-body
|
(5) | ||||||||
class-name
::
class-name
(
single-parameter-list
) = default;
|
(6) | ||||||||
| class-name | - | la clase cuyo constructor de movimiento se está declarando |
| parameter-list | - |
una
lista de parámetros
no vacía que satisface todas las siguientes condiciones:
|
| single-parameter-list | - | una lista de parámetros de un solo 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 constructor de movimiento |
Explicación
struct X { X(X&& other); // constructor de movimiento // X(X other); // Error: tipo de parámetro incorrecto }; union Y { Y(Y&& other, int num = 1); // constructor de movimiento con múltiples parámetros // Y(Y&& other, int num); // Error: `num` no tiene argumento predeterminado };
El constructor de movimiento normalmente se llama cuando un objeto es inicializado (mediante inicialización directa o inicialización por copia ) a partir de rvalue (xvalue o prvalue) (hasta C++17) xvalue (desde C++17) del mismo tipo, incluyendo
-
inicialización:
T a
=
std
::
move
(
b
)
;
o
T a
(
std
::
move
(
b
)
)
;
, donde
b
es de tipo
T; -
paso de argumentos de función:
f
(
std
::
move
(
a
)
)
;
, donde
a
es de tipo
Ty f es void f ( T t ) ; -
retorno de función:
return
a
;
dentro de una función como
T f
(
)
, donde
a
es de tipo
Tque tiene un constructor de movimiento.
Cuando el inicializador es un prvalue, la llamada al constructor de movimiento es frecuentemente optimizada (hasta C++17) nunca realizada (desde C++17) , ver copy elision .
Los constructores 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 un estado válido pero indeterminado. Dado que el constructor de movimiento no cambia el tiempo de vida del argumento, el destructor normalmente será llamado en el argumento en un momento posterior. Por ejemplo, mover desde un std::string o desde un std::vector puede resultar en que el argumento quede vacío. Para algunos tipos, como std::unique_ptr , el estado después del movimiento está completamente especificado.
Constructor de movimiento declarado implícitamente
Si no se proporcionan constructores 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 copy assignment operators declarados por el usuario;
- no hay move assignment operators declarados por el usuario;
- no hay destructor declarado por el usuario.
Entonces el compilador declarará un constructor de movimiento como un miembro explicit inline public no explícito de su clase con la firma T :: T ( T && ) .
Una clase puede tener múltiples constructores de movimiento, por ejemplo, tanto T :: T ( const T && ) como T :: T ( T && ) . Si existen algunos constructores de movimiento definidos por el usuario, el usuario aún puede forzar la generación del constructor de movimiento declarado implícitamente con la palabra clave default .
El constructor de movimiento declarado implícitamente (o predeterminado en su primera declaración) 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) .
Constructor de movimiento definido implícitamente
Si el constructor 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 ODR-use o needed for constant evaluation . Para tipos union, el constructor de movimiento definido implícitamente copia la representación del objeto (como mediante std::memmove ). Para tipos de clase no union, el constructor de movimiento realiza un movimiento completo miembro a miembro de los subobjetos base directos y subobjetos miembro del objeto, en su orden de inicialización, usando inicialización directa con un argumento xvalue . Para cada miembro de datos no estáticos de tipo referencia, el constructor de movimiento enlaza la referencia al mismo objeto o función al cual está enlazada la referencia fuente.
Si esto satisface los requisitos de un
constexpr
constructor
(until C++23)
constexpr
function
(since C++23)
, el constructor de movimiento generado es
constexpr
.
Constructor de movimiento eliminado
El constructor de movimiento declarado implícitamente o establecido por defecto explícitamente para la clase
T
se define como eliminado si
T
tiene un
subobjeto potencialmente construido
de tipo clase
M
(o posiblemente un arreglo multidimensional del mismo) tal que
-
Mtiene un destructor que está eliminado o es inaccesible desde el constructor de copia, o -
la resolución de sobrecarga aplicada para encontrar el constructor 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.
Tal constructor es ignorado por la resolución de sobrecarga (de lo contrario impediría la inicialización por copia desde un rvalue).
Constructor de movimiento trivial
El constructor de movimiento para la clase
T
es trivial si se cumplen todas las siguientes condiciones:
- 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 constructor de movimiento seleccionado para cada base directa de
Tes trivial; -
el constructor de movimiento seleccionado para cada miembro de tipo clase (o arreglo de tipo clase) no estático de
Tes trivial.
Un constructor de movimiento trivial es un constructor que realiza la misma acción que el constructor 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 movibles.
Constructor de movimiento elegible
|
Un constructor de movimiento es elegible si no está eliminado. |
(until C++20) |
|
Un constructor de movimiento es elegible si se satisfacen todas las siguientes condiciones:
|
(since C++20) |
La trivialidad de los constructores de movimiento elegibles determina si la clase es un tipo de duración implícita , y si la clase es un tipo trivialmente copiable .
Notas
Para hacer posible la garantía fuerte de excepción , los constructores de movimiento definidos por el usuario no deberían lanzar excepciones. Por ejemplo, std::vector depende de std::move_if_noexcept para elegir entre mover y copiar cuando los elementos necesitan ser reubicados.
Si se proporcionan tanto el constructor de copia como el de movimiento y no hay otros constructores viables, la resolución de sobrecarga selecciona el constructor de movimiento si el argumento es un rvalue del mismo tipo (un xvalue como el resultado de std::move o un prvalue como un temporal sin nombre (until C++17) ), y selecciona el constructor de copia si el argumento es un lvalue (objeto con nombre o una función/operador que retorna referencia a lvalue). Si solo se proporciona el constructor de copia, todas las categorías de argumento lo seleccionan (siempre que tome una referencia a const, ya que los rvalues pueden vincularse a referencias const), lo que hace que la copia sea el respaldo para el movimiento, cuando este no está disponible.
Ejemplo
#include <iomanip> #include <iostream> #include <string> #include <utility> struct A { std::string s; int k; A() : s("test"), k(-1) {} A(const A& o) : s(o.s), k(o.k) { std::cout << "move failed!\n"; } A(A&& o) noexcept : s(std::move(o.s)), // explicit move of a member of class type k(std::exchange(o.k, 0)) // explicit move of a member of non-class type {} }; A f(A a) { return a; } struct B : A { std::string s2; int n; // implicit move constructor B::(B&&) // calls A's move constructor // calls s2's move constructor // and makes a bitwise copy of n }; struct C : B { ~C() {} // destructor prevents implicit move constructor C::(C&&) }; struct D : B { D() {} ~D() {} // destructor would prevent implicit move constructor D::(D&&) D(D&&) = default; // forces a move constructor anyway }; int main() { std::cout << "Trying to move A\n"; A a1 = f(A()); // return by value move-constructs the target // from the function parameter std::cout << "Before move, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; A a2 = std::move(a1); // move-constructs from xvalue std::cout << "After move, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; std::cout << "\nTrying to move B\n"; B b1; std::cout << "Before move, b1.s = " << std::quoted(b1.s) << "\n"; B b2 = std::move(b1); // calls implicit move constructor std::cout << "After move, b1.s = " << std::quoted(b1.s) << "\n"; std::cout << "\nTrying to move C\n"; C c1; C c2 = std::move(c1); // calls copy constructor std::cout << "\nTrying to move D\n"; D d1; D d2 = std::move(d1); }
Salida:
Trying to move A Before move, a1.s = "test" a1.k = -1 After move, a1.s = "" a1.k = 0 Trying to move B Before move, b1.s = "test" After move, b1.s = "" Trying to move C move failed! Trying to move D
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 1353 | C++11 |
las condiciones donde los constructores de movimiento por defecto
se definen como eliminados no consideraban tipos de arreglos multidimensionales |
considerar estos tipos |
| CWG 1402 | C++11 |
un constructor de movimiento por defecto que llamaría
a un constructor de copia no trivial se definía como eliminado; un constructor de movimiento por defecto que está eliminado aún participaba en la resolución de sobrecarga |
permite llamada a dicho constructor
de copia; se hace ignorado en la resolución de sobrecarga |
| CWG 1491 | C++11 |
un constructor de movimiento por defecto de una clase con un miembro
de datos no estático de tipo referencia a valor-r se definía como eliminado |
no eliminado en este caso |
| CWG 2094 | C++11 |
un subobjeto volátil hacía no trivial un constructor
de movimiento por defecto ( CWG issue 496 ) |
la trivialidad no se ve afectada |
| CWG 2595 | C++20 |
un constructor de movimiento no era elegible si existe
otro constructor de movimiento que está más restringido pero no satisface sus restricciones asociadas |
puede ser elegible en este caso |