Espacios de nombres
Variantes
Acciones

Constructores de movimiento

De cppreference.com
< cpp‎ | language
 
 
 
 

Un constructor de movimiento de una clase T es un constructor de no plantilla cuyo primer parámetro es T&&, const T&&, volatile T&&, o const volatile T&&, y bien, no hay otros parámetros, o el resto de los parámetros tienen valores por defecto.

Contenido

[editar] Sintaxis

nombre_de_clase ( nombre_de_clase && ) (1) (desde C++11)
nombre_de_clase ( nombre_de_clase && ) = default; (2) (desde C++11)
nombre_de_clase ( nombre_de_clase && ) = delete; (3) (desde C++11)

Donde nombre_de_clase debe referirse a la clase actual (o la instanciación actual de una plantilla de clase), o, cuando se declara en un espacio de nombre o en una declaración friend, debe ser un nombre de clase calificado.

[editar] Explicación

1) Declaración típica de un constructor de movimiento.
2) Forzar a que el compilador genere un constructor de movimiento.
3) Evitar un constructor de movimiento implícito.

El constructor de movimiento se llama típicamente cuando un objeto es inicializado (mediante la inicialización directa o la inicialización de copia) a partir de un 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 tipo T;
  • paso de argumentos a función: f(std::move(a));, donde a es del tipo T y f es void f(T t);
  • retorno de función: return a; dentro de una función tal como T f(), donde a es del tipo T que tiene un constructor de movimiento.

Cuando el inicializador es un prvalue, la llamada al constructor de movimiento es frecuentemente optimizada (hasta C++17)nunca se hace (desde C++17). Véase elisión de copia.

Los constructores de movimiento típicamente "roban" los recursos albergados por el argumento (p. ej., punteros a objetos asignados dinámicamente, descriptores de archivos, sockets TCP, flujos de E/S, hilos en ejecución, etc.) en lugar de hacer copias de ellos, y dejan el argumento en algún estado válido pero indeterminado. Por ejemplo, mover desde una cadena (std::string) o desde un std::vector puede resultar en que el argumento se quede vacío. Sin embargo, no se puede depender de este comportamiento. Para algunos tipos, tales como std::unique_ptr, el estado posterior al movimiento está completamente especificado.

[editar] 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:

entonces el compilador declarará un constructor de movimiento como un miembro no explicit inline public de su clase con la signatura T::T(T&&).

Una clase puede tener múltiples constructores de movimiento (p. ej., tanto T::T(const T&&) como T::T(T&&)). Si se encuentran presentes 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 por defecto en su primera declaración) tiene una especificación de excepción como se describe en la especificación de excepción dinámica (hasta C++17)especificación de excepción (desde C++17)

[editar] Constructor de movimiento declarado implícitamente eliminado

El constructor de movimiento declarado implícitamente o por defecto para una clase T se define como eliminado si cualquiera de lo siguiente es verdadero:

  • T tiene datos miembro estáticos que no pueden moverse (tiene constructores de movimiento eliminados, inaccesibles o ambiguos);
  • T tiene una clase base directa o virtual que no puede moverse (tiene constructores de movimiento eliminados, inaccesibles o ambiguos);
  • T tiene una clase base directa o virtual o un miembro de datos no estático con un destructor eliminado o inaccesible;
  • T es una clase similar a una unión y tiene un miembro variante con un constructor de movimiento no trivial.

Un constructor de movimiento deducido como por defecto que está eliminado se ignora por la resolución de sobrecarga (de lo contrario prevendría la inicialización de copia a partir de un rvalue).


[editar] Constructor de movimiento trivial

El constructor de movimiento para una clase T es trivial si todo lo siguiente es verdadero:

  • no es proporcionado por el usuario (lo que significa que 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 clase base directa de T es trivial;
  • el constructor de movimiento seleccionado para cada tipo de clase no estático (o array 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, genera una copia de la representación del objeto como si lo fuera mediante std::memmove. Todos los tipos de datos compatibles con el lenguaje C (tipos POD) son trivialmente movibles.

[editar] Constructor de movimiento seleccionable

Un constructor de movimiento es seleccionable si no está eliminado.

(hasta C++20)

Un constructor de movimiento es seleccionable si

(desde C++20)

La trivialidad de los constructores de movimiento seleccionables determina si la clase es un tipo con tiempo de vida implícito, y si la clase es un tipo copiable trivialmente.

[editar] 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 un cuerpo de función y se compila) por el compilador si hubo uso ODR o se necesita para la evaluación constante. Para los tipos de union, el constructor de movimiento definido implícitamente copia la representación del objeto (como si se hiciera mediante std::memmove). Para los tipos de clase de no unión (class y struct), el constructor de movimiento realiza un movimiento completo miembro por miembro de las bases y los miembros no estáticos del objeto, en su orden de inicialización, usando la inicialización directa con un argumento xvalue.

Si esto satisface los requerimientos de un constructor constexpr (hasta C++23)función constructor (desde C++23), el constructor de movimiento generado es constexpr.

(desde C++11)

[editar] Notas

Para hacer posible la garantía de excepción fuerte, los constructores de movimiento definidos por el usuario no deberán lanzar excepciones. Por ejemplo, std::vector depende de std::move_if_noexcept para escoger entre la copia o movimiento cuando los elementos necesitan reubicarse.

Si se proporcionan tanto constructores de copia como de movimiento y otros constructores no son viables, la resolución de sobrecarga selecciona el constructor de movimiento si el argumento es un rvalue del mismo tipo (un xvalue tal como el resultado de std::move o un prvalue tal como un temporal sin nombre (hasta C++17)), y selecciona el constructor de copia si el argumento es un lvalue (un objeto nombrado o una función u operador que devuelve una referencia lvalue). Si solamente se proporciona el constructor de copia, todas las categorías de argumentos lo seleccionan (siempre y cuando tome una referencia a const, ya que los rvalues pueden vincularse a referencias a const), lo que hace la copia el respaldo del movimiento, cuando el movimiento no se encuentra disponible.

Un constructor se llama un constructor de movimiento cuando toma una referencia rvalue como un parámetro. No está obligado a mover nada, no se requiere que la clase tenga un recurso para ser movido, y un constructor de movimiento puede no ser capaz de mover un recurso como en el caso permisible (pero no necesariamente sensato) donde el parámetro es una referencia a un rvalue constante (const T&&).

[editar] Ejemplo

#include <string>
#include <iostream>
#include <iomanip>
#include <utility>
 
struct A
{
    std::string s;
    int k;
 
    A() : s("prueba"), k(-1) { }
    A(const A& o) : s(o.s), k(o.k) { std::cout << "¡falla al mover!\n"; }
    A(A&& o) noexcept :
           s(std::move(o.s)),       // movimiento explícito de un miembro de tipo clase
           k(std::exchange(o.k, 0)) // movimiento explícito de un miembro de tipo no clase
    { }
};
 
A f(A a)
{
    return a;
}
 
struct B : A
{
    std::string s2;
    int n;
    // constructor de movimiento implícito B::(B&&)
    // llama al constructor de movimiento de A
    // llama al constructor de movimiento de s2
    // y genera una copia bit por bit de n
};
 
struct C : B
{
    ~C() { } // el destructor evita el constructor de movimiento implícito C::(C&&)
};
 
struct D : B
{
    D() { }
    ~D() { }          // el destructor evitaría el constructor de movimiento implícito 
                      // D::(D&&)
    D(D&&) = default; // de todos modos fuerza  un constructor de movimiento
};
 
int main()
{
    std::cout << "Intentando mover a A\n";
    A a1 = f(A()); // retorno por valor construye mediante movimiento
                   // el objeto destino a partir del parámetro de función
 
    std::cout << "Antes del movimiento, a1.s = " << std::quoted(a1.s) << " a1.k = " 
        << a1.k << '\n';
 
    A a2 = std::move(a1); // construye mediante movimiento a partir de un xvalue
 
    std::cout << "Posterior al movimiento, a1.s = " << std::quoted(a1.s) << " a1.k = " 
        << a1.k << '\n';
 
    std::cout << "\nIntentando mover a B\n";
    B b1;
    std::cout << "Antes del movimiento, b1.s = " << std::quoted(b1.s) << "\n";
 
    B b2 = std::move(b1); // llama al contructor de movimiento implícito
    std::cout << "Posterior al movimiento, b1.s = " << std::quoted(b1.s) << "\n";
 
    std::cout << "\nIntentando mover a C\n";
    C c1;
    C c2 = std::move(c1); // llama al contructor de copia
 
    std::cout << "Intentando mover a D\n";
    D d1;
    D d2 = std::move(d1);
}

Salida:

Intentando mover a A
Antes del movimiento, a1.s = "prueba" a1.k = -1
Posterior al movimiento, a1.s = "" a1.k = 0
 
Intentando mover a B
Antes del movimiento, b1.s = "prueba"
Posterior al movimiento, b1.s = ""
 
Intentando mover a C
¡falla al mover!
 
Intentando mover a D

[editar] Informes de defectos

Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.

ID Aplicado a Comportamiento según lo publicado Comportamiento correcto
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 se había definido como eliminado (=delete)
todavía participaba en la resolución de sobrecarga.
Permite llamar a tal constructor
de copia; hecho 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 rvalue se definía como eliminado
no eliminado en este caso
CWG 2094 C++11 Un subobjeto volatile hacía un constructor de movimiento por defecto
no trivial (CWG 496).
La trivialidad no se afecta.

[editar] Véase también