c++ c++11 move-semantics

c++ - ¿Cuál es el comportamiento del constructor de movimiento generado por el compilador?



c++11 move-semantics (6)

¿ std::is_move_constructible<T>::value == true implica que T tiene un constructor de movimiento utilizable? Si es así, ¿cuál es el comportamiento predeterminado de ella?

Considere el siguiente caso:

struct foo { int* ptr; }; int main() { { std::cout << std::is_move_constructible<foo>::value << ''/n''; foo f; f.ptr = (int*)12; foo f2(std::move(f)); std::cout << f.ptr << '' '' << f2.ptr << ''/n''; } return 0; }

y el resultado es:

1 0000000C 0000000C

Pensé que f.ptr debería ser nullptr . Así que en este caso,

  1. ¿Se construye el movimiento f2 ?
  2. Si es así, ¿no debería ser invalidado el valor?
  3. ¿Cómo puedo saber si las instancias de una clase se pueden mover-construir correctamente (invalidar la anterior)?

(Estoy usando VS11.)

Actualizar

El comportamiento predeterminado de mover constructor es el mismo que un constructor de copia, ¿es correcto? Si es verdad

  1. Siempre esperamos que un ctor de movimiento robe los recursos del objeto movido desde, mientras que el predeterminado no se comporta como se espera, entonces ¿cuál es el punto de tener un ctor de movimiento predeterminado?
  2. ¿Cómo puedo saber si una clase tiene un constructor de movimiento personalizado (que se puede garantizar que se comporte correctamente)?

Parece que foo f2(std::move(f)); llama a la copia ctor cuando declaré uno, ver:

struct foo { int* ptr; foo() {} foo(const foo& other) { std::cout << "copy constructed/n"; } }; int main() { { std::cout << std::is_move_constructible<foo>::value << ''/n''; foo f; foo f2(std::move(f)); } system("pause"); return 0; }

Ahora la salida es:

1 copy constructed

Si foo tiene un constructor de movimientos, ¿no lo llamaría foo f2(std::move(f)) ?

Así que ahora mis preguntas son: ¿Cómo saber si una clase tiene un ctor de movimiento y, si lo tiene, cómo puedo llamarlo explícitamente?

Lo que estoy tratando de hacer es ...

template<typename T, bool has_move_ctor> struct MoveAux; template<typename T> struct MoveAux<T, true> { static void doMove(T* dest, T* src) { new(dest) T(std::move(*src)); //move ctor } }; template<typename T> struct MoveAux<T, false> { static void doMove(T* dest, T* src) { new(dest) T(*src); //copy ctor src->~T(); } }; template<typename T> inline doMove(T* dest, T* src) { MoveAux<T,/*a trait*/>::doMove(dest, src); }

Así que pensé que std::is_move_constructible<T>::value se puede pasar a la plantilla, mientras que ahora veo que a este rasgo solo le importa si T t(T()) es una expresión válida, puede llamar T::T(const T&) . Ahora suponga que T es una clase personalizada, entonces quiero que las plantillas anteriores se comporten como:

  1. Si no declaro un ctor de movimiento, quiero que el método de plantilla llame a MoveAux<T,false>::doMove .
  2. Si declaro uno, necesito que llame a MoveAux<T,true>::doMove .

¿Es posible hacer este trabajo?


¿ std::is_move_constructible<T>::value == true implica que T tiene un constructor de movimiento utilizable?

Ya sea un constructor de movimiento o un constructor de copia. Recuerde que la operación de construcción de copia satisface todos los requisitos que se colocan sobre la construcción de movimiento de operación, y algunos más.

En términos estándar, un objeto MoveConstructible es uno para el cual la evaluación de la expresión:

T u = rv;

hace u equivalente al valor de rv antes de la construcción; el estado de rv después de ser movido desde no se especifica . Pero como no está especificado, esto significa que el estado podría ser incluso idéntico al que tenía rv antes de ser trasladado: en otras palabras, u podría ser una copia de rv .

De hecho, el Estándar define el concepto CopyConstructible como un refinamiento del concepto MoveConstructible (de modo que todo lo que es CopyConstructible también es MoveConstructible , pero no al revés).

Si es así, ¿cuál es el comportamiento predeterminado de la misma?

El comportamiento de un constructor de movimiento generado implícitamente es realizar un movimiento de miembros de los miembros de datos del tipo para el que se genera.

Según el párrafo 12.8 / 15 del Estándar C ++ 11:

El constructor de copia / movimiento definido de forma implícita para una clase X no sindicalizada realiza una copia / movimiento de sus bases y miembros . [Nota: se ignoran los inicializadores brace-o-igual de miembros de datos no estáticos. Véase también el ejemplo en 12.6.2. "Nota final"

Además:

1 - se construye el movimiento f2 ?

Sí.

2 - Si es así, ¿no debería ser invalidado el valor de r?

Mover un puntero es lo mismo que copiarlo. Así que no está ocurriendo ninguna invalidación, ni debería estar sucediendo. Si desea un constructor de movimiento que deje el objeto movido desde en un estado particular (es decir, establece un miembro de datos de puntero en nullptr ), debe escribir el suyo propio o delegar esta responsabilidad a alguna clase de puntero inteligente como std::unique_ptr .

Observe que la palabra " invalidado " no es correcta aquí. Los constructores de movimiento (así como los operadores de asignación de movimiento) están destinados a dejar el objeto movido desde en un estado válido (aún sin especificar).

En otras palabras, el invariante de clase debe ser respetado, y debería ser posible invocar en un objeto movido desde operaciones que no tienen ninguna condición previa en su estado (generalmente, destrucción y asignación).


¿std :: is_move_constructible :: value == true implica que T tiene un constructor de movimiento utilizable?

No. Indica que puede tomar una expresión rvalue del tipo de objeto y construir un objeto a partir de él . Si esto utiliza el constructor de movimiento o el constructor de copia no es relevante para este rasgo.

es f2 se construye el movimiento?

Sí.

Si es así, ¿no debería ser invalidado el valor?

No. No es así como funciona el movimiento.

¿cómo puedo saber si las instancias de una clase se pueden mover-construir correctamente (invalidar la anterior)?

Esa no es ninguna definición de "adecuadamente construido por movimiento" que existe. Si desea "invalidar el anterior", entonces tendrá que hacerlo usted mismo.

La construcción de movimientos generalmente no garantiza nada sobre el estado del objeto antiguo. Estará en un estado válido pero indefinido. Tal estado puede ser en gran medida "el mismo que era antes". Mover construcción para un puntero es lo mismo que copiar el puntero.

Si desea "invalidar" después de un movimiento, debe escribir su propio constructor de movimientos que lo haga explícitamente.

(Estoy usando VS11)

Entonces no tienes ningún constructor de movimiento generado por el compilador. No es que importe, ya que los constructores de mover y copiar para los punteros hacen lo mismo.


Si foo tiene un constructor de movimientos, ¿no lo llamaría foo f2 (std :: move (f))? No obtiene el constructor de movimiento predeterminado cuando suministra su constructor de copia. Agregue la siguiente línea para obtenerlo (y observe el cambio). foo (foo && ifm) = predeterminado;


el comportamiento predeterminado del constructor de movimiento es el mismo que el de un constructor de copia, ¿es correcto? si es verdad

No. Está mal. Es cierto sólo para los primitivos. Es similar a la de copia constructor.

El constructor de copia generado por defecto llama al constructor de copia de todos sus miembros en el orden declarado

Pero el constructor de movimiento generado por defecto llama al constructor de movimiento de todos sus miembros en el orden declarado

Ahora, la siguiente pregunta es: ¿qué hace el constructor de copiar / mover de los primitivos int s float s pointer s?

Respuesta: Solo copian los valores (copian y mueven el constructor)


Tenga en cuenta que Visual Studio 2012 / VC ++ 11 no admite constructores de movimiento generados por el compilador; de hecho, considere esta cita de la publicación del blog "C ++ 11 Features in Visual C ++ 11" (el énfasis es mío):

Rvalue references v3.0 agrega nuevas reglas para generar automáticamente constructores de movimiento y operadores de asignación de movimiento bajo ciertas condiciones. Esto no se implementará en VC11 , que continuará siguiendo el comportamiento de VC10 de no generar automáticamente constructores de movimiento / operadores de asignación de movimiento .

Con los punteros sin procesar , debe definir los constructores de movimientos por sí mismo, borrando manualmente el antiguo puntero "movido de:"

class Foo { public: // Move constructor Foo(Foo&& other) : m_ptr(other.m_ptr) // copy pointer value { // Clear out old "moved-from" pointer, to avoid dangling references other.m_ptr = nullptr; } private: int* m_ptr; };

En cambio, si usa un puntero inteligente como std::unique_ptr , move constructor está definido correctamente, y puede llamar a std::move :

class Foo { public: // Move constructor Foo(Foo&& other) : m_ptr(std::move(other.m_ptr)) // move from other, // old pointer automatically cleared { } private: std::unique_ptr<int> m_ptr; };

Con los constructores de movimiento generados automáticamente , no tiene que definir un constructor de movimiento personalizado explícitamente, si el movimiento de miembro es correcto para usted.


n3376 12.8 / 15

El constructor de copia / movimiento definido de forma implícita para una clase X no sindicalizada realiza una copia / movimiento de sus bases y miembros.

Cada miembro de datos base o no estático se copia / mueve de la manera apropiada para su tipo:

- si el miembro es una matriz, cada elemento se inicializa directamente con el subobjeto correspondiente de x;

- si un miembro m tiene el tipo de referencia de valor R T&&, se inicializa directamente con static_cast (xm);

- de lo contrario, la base o miembro se inicializa directamente con la base o miembro correspondiente de x.