c++ c++11 copy-constructor move-semantics move-constructor

c++ - ¿Por qué se copian y mueven constructores llamados juntos?



c++11 copy-constructor (2)

A (10), A (20), A (30) son temporales, ¿verdad?

Correcto.

Entonces, ¿por qué se llama el constructor de copia? ¿No debería ser llamado el constructor de movimiento en su lugar?

Desafortunadamente, no es posible pasar de std::initializer_list , que es lo que utiliza este constructor de std::vector .

Pasar mover (A (10)), mover (A (20)), mover (A (30)) en su lugar

En este caso se llama copia o mover constructor. ¿Qué esta pasando?

Debido a que la conversión de std::move evita la elección de copia, los elementos de std::initializer_list se construyen sin elision. Luego el constructor de vectores copia de la lista.

Considere el siguiente código:

#include <iostream> #include <vector> using namespace std; class A { public: A(int) { cout << "int" << endl; } A(A&&) { cout << "move" << endl; } A(const A&) { cout << "copy" << endl; } }; int main() { vector<A> v { A(10), A(20), A(30) }; _getch(); return 0; }

La salida es:

int int int copy copy copy

A(10) , A(20) y A(30) son temporales, ¿verdad?

Entonces, ¿por qué se llama el constructor de copia? ¿No debería ser llamado el constructor de movimiento en su lugar?

Al pasar el move(A(10)) , move(A(20)) , move(A(30)) lugar, la salida es:

int move int move int move copy copy copy

En este caso se llama copiar o mover constructor.

¿Qué esta pasando?


std::vector puede construirse a partir de std::initializer_list , y usted está llamando a ese constructor. Las reglas para la construcción initializer_list indican que este constructor es agresivamente preferido:

Un constructor es un constructor de lista de inicializadores si su primer parámetro es de tipo std::initializer_list<E> o referencia a std::initializer_list<E> calificado std::initializer_list<E> para algún tipo E , y no hay otros parámetros o bien Todos los demás parámetros tienen argumentos por defecto (8.3.6). [Nota: los constructores de lista de inicializadores se favorecen sobre otros constructores en la inicialización de lista <...>]

Además, debido al tipo de implementación extraña de una lista de initializer_list como una matriz asignada bajo el capó, los elementos de la matriz correspondiente a la que hace referencia el std::initializer_list<E> están obligados a ser copiados inicializados (que pueden eliminarse):

Un objeto de tipo std::initializer_list<E> se construye a partir de una lista de inicializadores como si la implementación asignara una matriz de N elementos de tipo E , donde N es el número de elementos en la lista de inicializadores. Cada elemento de esa matriz se inicializa con copia con el elemento correspondiente de la lista de inicializadores, y el objeto std::initializer_list<E> se construye para referirse a esa matriz

(Ambas referencias anteriores de N3337 [dcl.init.list])

Sin embargo, en su primer ejemplo, las copias pueden ser eliminadas a pesar del nombre ([dcl.init] / 14), por lo que no verá una construcción de copia adicional (también se pueden mover). Puede agradecerle a su compilador por eso, porque no es necesario copiar elision en C ++ 11 (aunque está en C ++ 17).

Consulte [class.copy] para obtener más detalles ("Cuando se cumplen ciertos criterios, una implementación puede omitir la construcción de copiar / mover de un objeto de clase ...").

La parte final es clave:

[support.initlist] declara que

Un objeto de tipo initializer_list<E> proporciona acceso a una matriz de objetos de tipo const E

Esto significa que el std::vector no puede tomar la memoria directamente; debe copiarse, aquí es donde finalmente se ven las construcciones de copia que se están llamando.

En el segundo ejemplo, como lo dijo Kerrek SB, impidió la elección de copia que mencioné anteriormente y causó una sobrecarga adicional de un movimiento.