¿Es posible evitar la llamada al constructor de copia en C++?
Estoy escribiendo una función de plantilla que acepta una clase personalizada (que puede ser cualquier clase o tipo primitivo) como argumento de plantilla, lee algunos datos (de ese tipo) desde un flujo de entrada, y luego los almacena en un mapa no ordenado similar a este:
std::unordered_map<CustomClass, std::vector
He implementado una clase personalizada para probar el comportamiento. He sobrecargado std::hash para que esta clase pueda ser almacenada en un mapa no ordenado como clave y he sobrecargado todos los operadores y constructores para que cada vez que son llamados, recibo un mensaje en la consola (por ejemplo, cuando se llama a un constructor de copia, recibo un mensaje “constructor de copia […datos…]”).
EDICIÓN: Como se solicitó en los comentarios, aquí está la definición e implementación de la clase personalizada (tenga en cuenta que la clase aquí es solo un marcador de posición para que podamos discutir la idea general detrás de esta pregunta. Soy muy consciente de que es tonto y no se debería implementar de esta manera. El código de los operadores >> y << no está aquí, para evitar el desorden)
class CustomClass {
public:
CustomClass(int a=0) {
std::cout << “constructor predeterminado” << std::endl;
m_data = a;
}
CustomClass(const CustomClass& other) {
std::cout << "constructor de copia " ;//<< std::endl;
m_data = other.m_data;
std::cout << "[" << m_data << "]" << std::endl;
}
CustomClass(CustomClass&& other) {
std::cout << "constructor de movimiento" << std::endl;
m_data = other.m_data;
}
CustomClass& operator=(const CustomClass& other) {
std::cout << "operador de asignación de copia" << std::endl;
if(this != &other){
m_data = other.m_data;
}
return *this;
}
CustomClass& operator=(CustomClass&& other) {
std::cout << "operador de asignación de movimiento" << std::endl;
if(this != &other){
m_data = other.m_data;
}
return *this;
}
~CustomClass() {
std::cout << "destructor" << std::endl;
}
int m_data;
};
Ahora mi pregunta es esta: ¿Es posible leer datos desde el flujo de entrada y construirlos en el lugar donde se necesitan sin llamar al constructor de copia?
Ejemplo de algunos códigos:
CustomClass x1; // llamada al constructor predeterminado
CustomClass x2; // llamada al constructor predeterminado
std::cout << “—-” << std::endl;
std::cin >> x1 >> x2; // mi entrada
std::cout << “—-” << std::endl;
map[x1].emplace_back(x2); // 2 llamadas al constructor de copia
std::cout << “—-” << std::endl;
std::cout << map[x1][0] << std::endl; // llamada al operador ==
std::cout << “—-” << std::endl;
Y aquí hay un ejemplo de salida de ese código:
constructor predeterminado
constructor predeterminado
[1]
[2]
constructor de copia [1]
constructor de copia [2]
operador ==
[2]
destructor
destructor
destructor
destructor
Me gustaría que cada objeto de esta clase se construyera solo una vez.
¿Es posible evitar estos constructores de copia? Si no ambos, al menos el que se llama durante la llamada a emplace_back(). ¿Es posible construir el objeto en el vector exactamente donde necesita estar en la memoria pero que esta llamada funcione para cada tipo?
Si necesito ampliar mi pregunta, por favor díganme en los comentarios, estaré encantado de hacerlo.
davy.ai
Sí, es posible construir el objeto en el lugar donde se necesita sin llamar al constructor de copia utilizando la función miembro emplace() del contenedor en lugar de emplace_back().
Aquí hay un ejemplo de cómo puede modificar su código para lograr esto:
En lugar de construir un objeto temporal CustomClass con
x2
al llamar aemplace_back()
, este código utilizastd::move(x2)
, lo que permite que el objeto se construya en el lugar utilizando un constructor de movimiento, en lugar de un constructor de copia.La función
emplace()
se utiliza para construir el objeto std::vector en el mapa y devolver un iterador a su ubicación en el mapa, y luego se utiliza la funciónfirst->second.emplace_back()
para insertar el objeto CustomClass dentro de ese vector en el lugar, sin llamar a ningún constructor de copia.Al utilizar emplace() y emplace_back() en lugar de intentar construir objetos y luego insertarlos o ubicarlos en el contenedor, se pueden evitar cualquier copia o movimiento innecesario y construir los objetos sólo una vez.