2009-12-01 14 views
10

El siguiente código solo funciona cuando el constructor de copia está disponible.Copy Constructor Necesario con el objeto temporal

Cuando agrego declaraciones de impresión (a través de std::cout) y hacer que el constructor de copia disponible no se utiliza (supongo que no hay truco tan compilador sucediendo para eliminar la copia innecesaria).

Pero tanto en la salida operator << y la función plop() a continuación (donde se crea un objeto temporal) No veo la necesidad de que el constructor de copia. ¿Alguien puede explicar por qué el lenguaje lo necesita cuando estoy pasando todo por referencia constante (o lo que estoy haciendo mal).

#include <iostream> 

class N 
{ 
    public: 
     N(int) {} 
    private: 
     N(N const&); 
}; 

std::ostream& operator<<(std::ostream& str,N const& data) 
{ 
    return str << "N\n"; 
} 

void plop(std::ostream& str,N const& data) 
{ 
    str << "N\n"; 
} 

int main() 
{ 
    std::cout << N(1);  // Needs copy constructor (line 25) 
    plop(std::cout,N(1)); // Needs copy constructor 

    N a(5); 
    std::cout << a; 
    plop(std::cout,a); 
} 

Compilador:

[Alpha:~/X] myork% g++ -v
Using built-in specs.
Target: i686-apple-darwin10
Configured with: /var/tmp/gcc/gcc-5646~6/src/configure --disable-checking --enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin10 --with-gxx-include-dir=/include/c++/4.2.1 --program-prefix=i686-apple-darwin10- --host=x86_64-apple-darwin10 --target=i686-apple-darwin10
Thread model: posix
gcc version 4.2.1 (Apple Inc. build 5646)

[Alpha:~/X] myork% g++ t.cpp
t.cpp: In function ‘int main()’:
t.cpp:10: error: ‘N::N(const N&)’ is private
t.cpp:25: error: within this context
t.cpp:10: error: ‘N::N(const N&)’ is private
t.cpp:26: error: within this context

Ésta es una versión simplificada de algún código real.
En el código real, tengo una clase que contiene un std :: auto_ptr. Esto significa que un constructor de copia que toma una referencia constante no es válida (sin algo de trabajo) y yo estaba recibiendo un error que indica que el constructor de copia no estaba disponible debido a que:

cambiar la clase también:

class N 
{ 
    public: 
     N(int) {} 
    private: 
     std::auto_ptr<int> data; 
}; 

El error es entonces:

t.cpp:25: error: no matching function for call to ‘N::N(N)’

+3

¿qué compilador? Esto compila bien en VC9 – Naveen

+0

N (N const &) debe ser N (const N &) –

+6

@Captain: No realmente. Ambos son validos Prefiero el formulario que uso arriba. –

Respuesta

15

De http://gcc.gnu.org/gcc-3.4/changes.html

When binding an rvalue of class type to a reference, the copy constructor of the class must be accessible. For instance, consider the following code:

class A 
{ 
public: 
    A(); 

private: 
    A(const A&); // private copy ctor 
}; 

A makeA(void); 
void foo(const A&); 

void bar(void) 
{ 
    foo(A());  // error, copy ctor is not accessible 
    foo(makeA()); // error, copy ctor is not accessible 

    A a1; 
    foo(a1);  // OK, a1 is a lvalue 
} 

This might be surprising at first sight, especially since most popular compilers do not correctly implement this rule (further details).

Este problema se solucionará en C++ 1x por Core Issue 391.

+0

Puedo ver la necesidad de un constructor de copia para la declaración 'foo (makeA());' ya que es el resultado de copiar el resultado de la función makeA() (incluso si el compilador elimina la copia real, debería estar allí). Pero la declaración 'foo (A())' no debería requerir una copia (aparte del estándar que lo requiere) que espero que el Issue 391 esté diseñado para resolver. –

5

Las partes aplicables del estándar aquí son §8.5.3/5, que cubre la inicialización de referencias y §3.10/6, que indica qué es un valor r y qué es un valor l (no siempre obvio en C++).

En este caso, la expresión de inicialización es: "N (1)", por lo que está creando explícitamente un objeto utilizando notación funcional. De acuerdo con 3.10/6, esa expresión es un valor r.

Luego tenemos que revisar las reglas en 8.5.3/5 en orden y usar la primera que corresponda. La primera posibilidad es si la expresión representa un valor l, o puede convertirse implícitamente en un valor l. Su expresión es un valor r, y la conversión implícita a un lvalue requeriría una función de conversión que devuelva una referencia, que no parece existir en este caso, por lo que no parece aplicarse.

La siguiente regla dice que la referencia debe ser para una const T (que es el caso aquí). En este caso, la expresión es un valor r de tipo de clase y es compatible con la referencia (es decir, la referencia es a la misma clase, o una base de la clase). Eso significa que la viñeta en la parte inferior de la página 151 (179 del PDF de C++ 2003) parece aplicarse. En este caso, el compilador puede vincular la referencia directamente al objeto que representa el valor r, O crear una copia temporal del valor r, y enlazar a esa copia temporal.

De cualquier forma, sin embargo, el estándar explícitamente requiere que: "El constructor que se usaría para hacer la copia se podrá llamar ya sea que la copia se haya realizado o no.. "

Como tal, creen que gcc es adecuado para dar un mensaje de error, y los otros son técnicamente equivocada para aceptar el código Simplifiqué su código un poco a lo siguiente:

class N { 
    public: 
     N(int) {} 
    private: 
     N(N const&); 
}; 

void plop(N const& data) { } 

int main() { 
    plop(N(1)); 
} 

cuando se invoca con "--A" (modo de errores estrictas), Comeau da el siguiente mensaje de error:

"plop.cpp", line 12: error: "N::N(const N &)", required for copy that was 
      eliminated, is inaccessible 
     plop(N(1)); 
     ^

mismo modo, cuando se invoca con (modo su "ANSI conforme") "/ Za", VC++ 9 da:

plop.cpp 
plop.cpp(12) : error C2248: 'N::N' : cannot access private member declared in class 'N' 
     plop.cpp(6) : see declaration of 'N::N' 
     plop.cpp(2) : see declaration of 'N' 
     while checking that elided copy-constructor 'N::N(const N &)' is callable 
     plop.cpp(6) : see declaration of 'N::N' 
     when converting from 'N' to 'const N &' 

Supongo que la mayoría de los otros compiladores hacen más o menos lo mismo. Como optimizan la llamada al constructor de copias, normalmente no requieren que exista o que esté accesible. Cuando les pide que se ajusten al estándar con la mayor precisión posible, dan el mensaje de error, porque técnicamente se requiere aunque no lo utilicen.

+0

¿Hay realmente una copia hecha aquí 'plop (N (1));'? Si no, no lo veo, ya que el compilador puede vincular la referencia directamente al objeto que representa el valor r. Por lo tanto, no se requiere la parte O de la conversación y, por lo tanto, no es necesario realizar una copia. Lamentablemente, es el siguiente párrafo (que obliga a un compilador conforme a producir un error) lo que me duele. –

+0

@Martin: No puedo imaginarme un compilador que realmente haga una copia, incluso con su nivel mínimo de optimización (aunque, por supuesto, * es * apenas posible). El lado positivo es que la mayoría de los compiladores (es decir, sensatos) normalmente no lo hacen cumplir, y con C++ 0x, el requisito desaparecerá. –

Cuestiones relacionadas