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.
Esta pregunta ya tiene una respuesta aquí:
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 astd::initializer_list<E>
calificadostd::initializer_list<E>
para algún tipoE
, 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 deN
elementos de tipoE
, dondeN
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 objetostd::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 tipoconst 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.