Un constructor de movimiento de la clase T
es un constructor sin plantilla cuyo primer parámetro es T&&, const T&&, volatile T&&, o const volatile T&&, y o bien no hay otros parámetros, o el resto de los parámetros tienen todos valores por defecto.
- Contenido
- Sintaxis
- Explicación
- Constructor de movimiento declarado implícitamente
- Constructor de movimiento declarado implícitamente eliminado
- Constructor de movimiento trivial
- Constructor move elegible
- Constructor de movimiento definido implícitamente
- Notas
- Ejemplo
- Informes de defectos
- Véase también
Contenido
- 1 Sintaxis
- 2 Explicación
- 3 Constructor de movimiento declarado implícitamente
- 4 Constructor de movimiento declarado implícitamente eliminado
- 5 Constructor de movimiento trivial
- 6 Constructor de movimiento elegible
- 7 Constructor de movimiento definido implícitamente
- 8 Notas
- 9 Ejemplo
- 10 Informes de defectos
- 11 Véase también
.constructor de movimientos definido implícitamente
Sintaxis
nombre_clase ( nombre_clase && ) |
(1) | (desde C++11) | |||||||
nombre_clase ( nombre_clase && ) = por defecto; |
(2) | (desde C++11) | |||||||
class_name ( class_name && ) = delete; |
(3) | (desde C++11) | |||||||
Donde class_name debe nombrar la clase actual (o la instanciación actual de una plantilla de clase), o, cuando se declara en el ámbito del espacio de nombres o en una declaración de amigo, debe ser un nombre de clase cualificado.
Explicación
El constructor move es típicamente llamado cuando un objeto es inicializado (por inicialización directa o por copia-inicialización) desde 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 del tipoT
; - paso de argumentos de funciones: f(std::move(a));, donde
a
es de tipoT
yf
es void f(T t); - función return: return a; dentro de una función como T f(), donde
a
es de tipoT
que tiene un constructor move.
Cuando el inicializador es un prvalue, la llamada al constructor move a menudo se optimiza (hasta C++17)nunca se hace (desde C++17), ver copy elision.
Los constructores move típicamente «roban» los recursos que tiene el argumento (e.g. punteros a objetos asignados dinámicamente, descriptores de archivo, sockets TCP, flujos de E/S, hilos en ejecución, etc.) en lugar de hacer copias de ellos, y dejar el argumento en algún estado válido pero indeterminado. Por ejemplo, pasar de un std::string o de un std::vector puede hacer que el argumento quede vacío. Sin embargo, no se debe confiar en este comportamiento. Para algunos tipos, como std::unique_ptr, el estado «moved-from» 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 (struct, class, o union), y todo lo siguiente es verdadero:
- no hay constructores de copia declarados por el usuario;
- no hay operadores de asignación de copia declarados por el usuario;
- no hay operadores de asignación de movimiento declarados por el usuario;
- no hay destructor declarado por el usuario.
entonces el compilador declarará un constructor de movimiento como un miembro no explícito inline public
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 algunos constructores de movimiento definidos por el usuario están presentes, el usuario todavía 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 por defecto en su primera declaración) tiene una especificación de excepciones como se describe en la especificación de excepciones dinámicas (hasta C++17)especificación de excepciones (desde C++17)
Constructor de movimiento declarado implícitamente eliminado
El constructor de movimiento declarado implícitamente o por defecto para la clase T
se define como eliminado si cualquiera de los siguientes es verdadero:
-
T
tiene miembros de datos no estáticos que no se pueden mover (tienen constructores de movimiento borrados, inaccesibles o ambiguos); -
T
tiene una clase base directa o virtual que no se puede mover (tiene constructores de movimiento borrados, inaccesibles o ambiguos); -
T
tiene clase base directa o virtual con un destructor borrado o inaccesible; -
T
es una clase tipo unión y tiene un miembro variante con constructor de movimiento no trivial.
Un constructor de movimiento por defecto que es borrado es ignorado por la resolución de sobrecarga (de lo contrario impediría la copia-inicialización de rvalue).
Constructor de movimiento trivial
El constructor de movimiento para la clase T
es trivial si todo lo siguiente es cierto:
- 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 constructor de movimiento seleccionado para cada base directa de
T
es trivial; - el constructor de movimiento seleccionado para cada tipo de clase no estática (o matriz de tipo de clase) miembro de
T
es 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 por std::memmove. Todos los tipos de datos compatibles con el lenguaje C (tipos POD) son trivialmente movibles.
Constructor move elegible
Un constructor move es elegible si no se borra. |
(hasta C++20) |
Un constructor de movimiento es elegible si
|
(desde C++20) |
La trivialidad de los constructores de movimiento elegibles determina si la clase es un tipo de vida implícita, y si la clase es un tipo trivialmente copiable.
Constructor de movimiento definido implícitamente
Si el constructor de movimiento declarado implícitamente no es ni borrado ni trivial, es definido (es decir, se genera y compila un cuerpo de función) por el compilador si se utiliza odr o se necesita para la evaluación constante. Para los tipos de unión, el constructor de movimiento definido implícitamente copia la representación del objeto (como por std::memmove). Para los tipos de clase que no son de unión (class y struct), el constructor move realiza un movimiento completo de las bases del objeto y de los miembros no estáticos, en su orden de inicialización, utilizando la inicialización directa con un argumento xvalue. Si esto satisface los requisitos de un constructor constexpr, el constructor move generado es constexpr
.
Notas
Para hacer posible la garantía de excepción fuerte, los constructores move definidos por el usuario no deberían lanzar excepciones. Por ejemplo, std::vector se basa en std::move_if_noexcept para elegir entre mover y copiar cuando los elementos necesitan ser reubicados.
Si se proporcionan los constructores copy y move y no hay otros constructores viables, la resolución de sobrecarga selecciona el constructor move 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 (hasta C++17)), y selecciona el constructor copy si el argumento es un lvalue (objeto con nombre o una función/operador que devuelve una referencia lvalue). Si sólo se proporciona el constructor de copia, todas las categorías de argumentos lo seleccionan (siempre que tome una referencia a const, ya que los rvalues pueden enlazarse a referencias const), lo que hace que la copia sea el fallback para mover, cuando mover no está disponible.
Un constructor se llama ‘move constructor’ cuando toma una referencia rvalue como parámetro. No está obligado a mover nada, la clase no está obligada a tener un recurso para ser movido y un ‘move constructor’ puede no ser capaz de mover un recurso como en el caso permitido (pero tal vez no sensible) donde el parámetro es una referencia rvalue const (const T&&).
Ejemplo
#include <string>#include <iostream>#include <iomanip>#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 << "Trying 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 << "Trying to move C\n"; C c1; C c2 = std::move(c1); // calls copy constructor std::cout << "Trying to move D\n"; D d1; D d2 = std::move(d1);}
Salida:
Trying to move ABefore move, a1.s = "test" a1.k = -1After move, a1.s = "" a1.k = 0Trying to move BBefore move, b1.s = "test"After move, b1.s = ""Trying to move Cmove failed!Trying to move D
Informes de defectos
Los siguientes informes de defectos que cambian el comportamiento se aplicaron retroactivamente a las normas C++ publicadas anteriormente.
DR | Aplicado a | Comportamiento como se publicó | Comportamiento correcto |
---|---|---|---|
CWG 1402 | C++11 | se eliminó un constructor de movimiento por defecto que llamaría a un constructor de copia no trivial; un constructor de movimiento por defecto que se elimina aún participaba en la resolución de sobrecarga |
permite llamar a dicho constructor de copia; hecho ignorado en la resolución de sobrecarga |
CWG 2094 | C++11 | un subobjeto volátil hecho de un constructor de movimiento predeterminado notrivial (CWG496) |
la trivialidad no se ve afectada |
Véase también
- constructor de conversión
- asignación de copia
- constructor de copia
- elisión de copia
- constructor por defecto
- destructor
- explícito
- inicialización
- inicialización agregada
- inicialización constante
- inicialización de copia
- inicialización por defecto
- inicialización directa
- inicializador de lista
- inicialización de lista
- inicialización de referencia
- inicialización de valor
- inicialización cero
- asignación de movimiento
- nuevo