2009-11-18 14 views
10

Aquí está el pequeño fragmento de código:¿Por qué no se llama al constructor de copia en este caso?

class A 
{ 
public: 
    A(int value) : value_(value) 
    { 
     cout <<"Regular constructor" <<endl; 
    } 

    A(const A& other) : value_(other.value_) 
    { 
     cout <<"Copy constructor" <<endl; 
    } 

private: 
    int value_; 
}; 
int main() 
{ 
    A a = A(5); 
} 

Supuse que la producción sería "Constructor regular" (por RHS), seguido de "constructor de copia" para LHS. Así que evité este estilo y siempre declare la variable de clase como A a(5);. Pero para mi sorpresa en el código anterior, el constructor de copias nunca se llama (Visual C++ 2008)

¿Alguien sabe si este comportamiento es el resultado de la optimización del compilador, o alguna característica documentada (y portátil) de C++? Gracias.

+0

temporal está optimizado, evitando la construcción + copia. Encuentro que es una buena suposición que ningún usuario construirá a partir de parámetros, de manera diferente a lo que se hace. Copiar-construir – jpinto3912

+0

Ver también: http: // stackoverflow.com/questions/1394229/understanding-return-value-optimization-and-returning-temporaries-c –

+1

En g ++, puede deshabilitar esta optimización con la opción -fno-elide-constructors – Fred

Respuesta

13

Desde otro comentario: "Así que por defecto no debe confiar en ella (ya que puede depender del compilador)"

No, no depende de que el compilador, prácticamente de todos modos. Cualquier compilador que valga un grano de arena no perderá tiempo construyendo una A y luego copiándola.

En el estándar dice explícitamente que es completamente aceptable que T = x; sea equivalente a decir T(x);. (§12.8.15, página 211) Hacer esto con T(T(x)) es obviamente redundante, por lo que elimina el interior T.

Para obtener el comportamiento deseado, que le obliga el compilador por defecto la construcción de la primera A:

A a; 
// A is now a fully constructed object, 
// so it can't call constructors again: 
a = A(5); 
+0

Gracias, GMan, nunca uso esta sintaxis de todos modos. Solo algo para recordar. Ah, acabo de encontrar en msdn: C++ estándar permite la elisión del constructor de copia (ver sección 12.8 Copiar objetos de clase, párrafo 15) – BostonLogan

+0

Gracias, lo veo ahora. Es un poco mucho para citar en una respuesta, así que solo lo referiré por número. – GManNickG

+1

Depende del compilador. El estándar permite un comportamiento diferente, mira mi respuesta. –

4

Aquí tienes copia-inicialización de a de A(5) temporal. La implementación permitió omitir el constructor de copia de llamada aquí según C++ Standard 12.2/2.

+0

No creo que sea correcto. El ejemplo en 12.2.2 involucra el paso temporal de una función antes de construir el objeto local. –

-1
A a = A(5); 

Esta línea es equivalente a

A a(5); 

pesar de su apariencia al estilo de la función, la primera línea simplemente construye a con el argumento 5. Prohibida la reproducción o temporales están involucrados. Del estándar C++, sección 12.1.11:

Se puede utilizar una conversión de tipo de notación funcional (5.2.3) para crear nuevos objetos de su tipo. [Nota: La sintaxis parece una llamada explícita del constructor. -finalizar]

+0

No, no es equivalente. Uno es * initialización de copia *, el otro es * inicialización directa *. –

4

Estaba investigando esto para responder a otra pregunta que se cerró como un engaño, así que para no dejar que el trabajo se pierda respondo éste en su lugar.

Una declaración del formulario A a = A(5) se llama copia-inicialización de la variable a. El estándar C++ 11, 8.5/16 estados:

La función seleccionada se invoca con la expresión del inicializador como su argumento; si la función es un constructor, la llamada inicializa un temporal de la versión cv no calificada del tipo de destino. El temporal es un valor prvero.El resultado de la llamada (que es el temporal para el caso del constructor) se usa para inicializar directamente, de acuerdo con a las reglas anteriores, el objeto que es el destino de la inicialización de la copia . En ciertos casos, se permite una implementación para eliminar la copia inherente en esta inicialización directa por construyendo el resultado intermedio directamente en el objeto que está siendo inicializado; ver 12.2, 12.8.

Esto significa que el compilador busca el constructor adecuado para manejar A(5), crea una copia temporal y temporal en que a. Pero, ¿bajo qué circunstancias se puede eliminar la copia?

Veamos lo 12.8/31 dice:

Cuando se cumplen ciertos criterios, se permite una implementación omitir la construcción copiar/mover de un objeto de clase, incluso si la copia/mover constructor y/o destructor para el objeto tienen efectos secundarios. En casos como el , la implementación trata el origen y el destino de la operación de copia/movimiento omitida como simplemente dos formas diferentes de referir al mismo objeto, y la destrucción de ese objeto se produce en el más adelante cuando el dos objetos habrían sido destruidos sin la optimización. Esta elisión de las operaciones de copiar/mover, llamada copia elisión, está permitido en las siguientes circunstancias (que pueden combinarse para eliminar múltiples copias):

[...]

  • cuando un objeto de clase temporal que no se haya vinculado a una referencia (12.2) se copiará/moverá a un objeto de clase con el mismo tipo de cv no calificado, se puede omitir la operación de copiar/mover construyendo el objeto temporal directamente en el objetivo de la copia/movimiento omitido

Tener todo esto en mente, aquí es lo que sucede con la expresión A a = A(5):

  1. El compilador ve una declaración con el copia-inicialización
  2. El constructor A(int) se selecciona para inicializar un objeto temporal
  3. Porque el objeto temporal es no vinculado a una referencia, y tiene el mismo tipo A que el de tipo de planificación en la expresión de inicialización de copia, el compilador puede construir directamente un objeto en a, elidiendo el
Cuestiones relacionadas