2010-11-11 17 views
35

Una pregunta reciente me hizo preguntarme sobre los constructores de copia explícitos. Aquí es un ejemplo de código que he intentado compilar bajo Visual Studio 2005:Comportamiento explícito del constructor de copias y usos prácticos

struct A 
{ 
    A() {} 
    explicit A(const A &) {} 
}; 

// #1 > Compilation error (expected behavior) 
A retByValue() 
{ 
    return A(); 
} 

// #2 > Compiles just fine, but why ? 
void passByValue(A a) 
{ 
} 

int main() 
{ 
    A a; 
    A b(a); // #3 > explicit copy construction : OK (expected behavior) 
    A c = a; // #4 > implicit copy construction : KO (expected behavior) 

    // Added after multiple comments : not an error according to VS 2005. 
    passByValue(a); 
    return 0; 
} 

Ahora para las preguntas:

  • es # 2 permitido por la norma? Si lo es, ¿cuál es la sección relevante que describe esta situación?
  • ¿Conoces algún uso práctico para un constructor de copia explícito?

[EDIT] Acabo de encontrar un enlace divertida en MSDN con la misma situación, y un comentario misteriosa de la función principal: "c se copia" (como si fuera obvio). Como señaló Oli Charlesworth: gcc no compila este código y creo que tiene razón para no hacerlo.

+0

No creo que los constructores de copias explícitos sean una buena idea. ¿Dónde leíste sobre ellos? – fredoverflow

+1

Esto parece estar corregido en VC++ 2010; da un error para la línea 'passByValue (a);'. – user200783

Respuesta

39

Creo que las secciones pertinentes de C++ 03 son §12.3.1 2:

An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax (8.5) or where casts (5.2.9 , 5.4) are explicitly used. A default constructor may be an explicit constructor; such a constructor will be used to perform default-initialization or value-initialization (8.5).

y § 8.5 12:

The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form

T x = a; 

The initialization that occurs in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization and is equivalent to the form

T x(a); 

Calling passByValue(a) implica copia-inicialización, no directo de inicialización, y por lo tanto debería ser un error, de acuerdo con C++ 03 § 12.3.1 2.

+2

Aunque las respuestas de MSalters y Oli son correctas, aceptaré esta porque deja en claro que VS está equivocado. Gracias ! – icecrime

+0

Muy buena respuesta, gracias. – ttvd

4

La definición de passByValue es correcta, porque no hay ninguna instrucción que copie un objeto A. En la definición de retByValue existe, por supuesto, una declaración de devolución que copia un objeto A.

+1

No estoy seguro de entender su respuesta.Para que quede claro: mantuve el código al mínimo, pero está bien que el compilador invoque 'passByValue (A())' desde la fuente principal, lo cual creo que requiere una copia implícita. – icecrime

+1

@icecrime: No, no puede llamar a 'passByValue()' así ... –

+0

Mi comprensión de esto es que el objeto 'a' nunca se usa dentro de' passByValue() ', por lo que el compilador lo ignora. – djeidot

2

Para ampliar la respuesta @MSalters' (que es correcta), si se va a añadir a su función passByValue(a);main(), el compilador habría debería quejarse.

Los constructores de copia explícitos son para prevenir exactamente esto, es decir, para evitar la copia implícita de recursos en llamadas a funciones y demás (esencialmente obliga al usuario a pasar por referencia en lugar de pasar por valor).

+0

No, no lo hará, passByValue (a); en main() compila bien, y yo, como icecrime, creo que hay una copia implícita del objeto. – davidnr

+0

@davidnr, hmm, falla en gcc por el motivo mencionado anteriormente. – Nim

+0

@davidnr: Bajo GCC, obtengo 'error: ninguna función de coincidencia para llamar a 'A :: A (A &)',' error: inicializando el argumento 1 de 'void passByValue (A)' ' –

3

Un uso práctico, antes de C++ 11, de hacer una copia del constructor exp lícito es en el caso en que en realidad es parte de hacer que una clase no se pueda copiar.

El peligro es que, aunque declaras el constructor de copias privado y no lo implementas, si accidentalmente copias uno en un amigo o en la misma clase, el compilador no lo recogerá y tú ' Solo obtendré un error de enlace difícil de encontrar.

Explicándolo también reduce la posibilidad de esto ya que el compilador puede recoger su copia no intencional y señalar la línea real donde lo está haciendo.

En C++ 11 (y 14) no es necesario hacer esto cuando se utiliza la sintaxis =delete, ya que se obtendría un error de compilación incluso si se copia dentro de la clase o dentro de un amigo.

Cuestiones relacionadas