2010-03-17 18 views
6

He aquí un extracto del artículo 56 del libro "C++ trampas":C++ copia-constructo constructo-y-asignar pregunta

No es raro ver a un simple inicialización de un objeto Y escrita cualquier de tres maneras diferentes, como si fueran equivalentes.

Y a(1066); 
Y b = Y(1066); 
Y c = 1066; 

De hecho, los tres de estos inicializaciones probablemente como resultado en el mismo código objeto que se genera , pero no son equivalentes. La inicialización de a se conoce como inicialización directa , y hace exactamente lo que uno podría esperar. La inicialización se realiza a través de una invocación directa de Y :: Y (int).

Las inicializaciones de byc son más complejas. De hecho, son demasiado complejos . Estas son las dos inicializaciones de copia . En el caso de la inicialización de b, estamos solicitando la creación de un temporal anónimo, inicializado con el valor 1066. Luego usamos este temporal anónimo como un parámetro para el constructor de copia para la clase Y para inicializar b. Finalmente, llamamos al destructor por el anónimo temporal.

Para probar esto, hice una clase simple con un miembro de datos (programa adjunto al final) y los resultados fueron sorprendentes. Parece que para el caso de c, el objeto fue construido por el constructor de copia en lugar de como se sugiere en el libro.

¿Alguien sabe si el estándar de idioma ha cambiado o es simplemente una característica de optimización del compilador? Yo estaba usando Visual Studio 2008.

Ejemplo de código:

#include <iostream> 

class Widget 
{ 
    std::string name; 
public: 
    // Constructor 
    Widget(std::string n) { name=n; std::cout << "Constructing Widget " << this->name << std::endl; } 
    // Copy constructor 
    Widget (const Widget& rhs) { std::cout << "Copy constructing Widget from " << rhs.name << std::endl; } 
    // Assignment operator 
    Widget& operator=(const Widget& rhs) { std::cout << "Assigning Widget from " << rhs.name << " to " << this->name << std::endl; return *this; } 
}; 

int main(void) 
{ 
    // construct 
    Widget a("a"); 
    // copy construct 
    Widget b(a); 
    // construct and assign 
    Widget c("c"); 
    c = a; 
    // copy construct! 
    Widget d = a; 
    // construct! 
    Widget e = "e"; 
    // construct and assign 
    Widget f = Widget("f"); 

    return 0; 
} 

Salida:

Constructing Widget a 

Copy constructing Widget from a 

Constructing Widget c 
Assigning Widget from a to c 

Copy constructing Widget from a 

Constructing Widget e 

Constructing Widget f 
Copy constructing Widget from f 

estaba más sorprendido por los resultados de la construcción dye. Para ser precisos, esperaba que se creara un objeto vacío, y luego un objeto para ser creado y asignado al objeto vacío. En la práctica, los objetos fueron creados por el constructor de copia.

+0

"En el caso de la inicialización de b, estamos solicitando la creación de un anónimo temporal de tipo Y, inicializado con el valor 1066" ... y lo mismo en la inicialización de c, es más difícil de ver el temporal. –

+0

Tenga en cuenta que 'Y c = 1006' no es posible si su constructor se declara como' explicit' ... como lo haría un constructor de parámetros, la mayoría de las veces. –

+0

Matthieu, sí, estoy de acuerdo con lo que dijiste y siempre lo hago cuando programo. Su punto también está cubierto en el libro "C++ más efectivo" si no recuerdo mal. – Andy

Respuesta

16

La sintaxis

X a = b; 

donde a y b son de tipo X siempre ha significado construcción copia.Independientemente de las variantes, tales como:

X a = X(); 

se utilizan, no hay ninguna tarea en curso, y nunca ha sido así. Construir y asignar sería algo así como:

X a; 
a = X(); 
+0

Gracias por el comentario sobre la construcción de copias. No era realmente consciente de eso, siempre pensé que tienes que hacer X = a (b); para estar seguro de invocar el constructor de copia. – Andy

6

El compilador puede optimizar los casos b y c para que coincidan con a. Además, el compilador puede eliminar por completo las llamadas al operador de construcción y asignación de copias, de modo que lo que ve no es necesariamente lo mismo con los diferentes compiladores o incluso con la configuración del compilador.

+0

¿Esto significa que el libro puede no ser 100% correcto ahora? Interesante. – Andy

+4

@Andy, no, el libro es 100% correcto. Pero no está 100% completo. Si desea completar el 100%, debe consultar el Estándar. Específicamente, esto (o la parte que citó) no explica que el compilador pueda optimizar copias si la copia se realiza desde un temporal como el anterior. –

+0

Gracias. Supongo que aún deberías usar los formatos sugeridos por el libro, en caso de que el compilador que estés utilizando no implemente la optimización. – Andy

1

A partir de C++ 17, los tres de estos son equivalente (a menos Y::Y(int) es explicit, lo que simplemente no permitir c) a causa de lo que se denomina mandatory copy elision.

Incluso Y c = 1066; crea sólo el Y objeto porque el resultado de la conversión implícita a Y es un prvalue que se utiliza para inicializar c en lugar de crear un temporal.