2009-12-01 3 views
10

Il seguente codice funziona solo quando il costruttore di copie è disponibile.Copia Costruttore necessario con oggetto temporaneo

Quando aggiungo istruzioni di stampa (tramite std::cout) e rende disponibile il costruttore di copie non viene utilizzato (presumo che si verifichi un tale accorgimento del compilatore per rimuovere la copia non necessaria).

Ma in entrambe l'uscita operator << e nella funzione plop() di seguito (dove creo un oggetto temporaneo) non vedo la necessità del costruttore di copie. Qualcuno può spiegare perché il linguaggio ne ha bisogno quando sto passando tutto per riferimento const (o cosa sto facendo male).

#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); 
} 

Compiler:

[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

Questa è una versione semplificata del codice vero e proprio.
Nel codice reale ho una classe che contiene uno std :: auto_ptr. Ciò significa che un costruttore di copia che richiede un riferimento const non è valido (senza qualche lavoro) e mi è stato sempre un errore che indica che il costruttore di copia non era disponibile a causa di esso:

cambiare la classe troppo:

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

L'errore è quindi:

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

+3

quale compilatore? Questo compila bene su VC9 – Naveen

+0

N (N const &) dovrebbe essere N (const N &) –

+6

@Captain: Non proprio. Entrambi sono validi. Preferisco il modulo che uso sopra. –

risposta

15

Da 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).

Questo verrà risolto in C++ 1x da Core Issue 391.

+0

Posso vedere la necessità di un costruttore di copia per l'istruzione 'foo (makeA());' poiché è il risultato della copia del risultato dalla funzione makeA() (anche se il compilatore elimina la copia effettiva dovrebbe essere lì). Ma la frase 'foo (A())' non dovrebbe richiedere una copia (a parte lo standard che lo richiede) che spero che il Problema 391 sia progettato per risolvere. –

5

Le parti applicabili dello standard sono il §8.5.3/5, che copre l'inizializzazione dei riferimenti e il §3.10/6, che indica cosa è un valore e cosa è un lvalue (non sempre ovvio in C++).

In questo caso, l'espressione di inizializzazione è: "N (1)", quindi stai creando in modo esplicito un oggetto utilizzando la notazione funzionale. Secondo la 3.10/6, quell'espressione è un valore.

Quindi dobbiamo seguire le regole in 8.5.3/5 in ordine, e usare il primo che si applica. La prima possibilità è se l'espressione rappresenta un lvalue o può essere convertita implicitamente in un lvalue. L'espressione è un valore rvalore e la conversione implicita a un valore di lvalue richiederebbe una funzione di conversione che restituisce un riferimento, che in questo caso non sembra esistere, pertanto non sembra applicabile.

La prossima regola dice che il riferimento deve essere a una const T (che è il caso qui). In questo caso, l'espressione è un valore di classe e è di riferimento compatibile con il riferimento (ovvero il riferimento è alla stessa classe o una base della classe). Ciò significa che il punto elenco in fondo a pagina 151 (179 del PDF C++ 2003) sembra applicarsi. In questo caso, il compilatore può legare il riferimento direttamente all'oggetto che rappresenta il valore, OPPURE creare una copia temporanea del valore di rvalue e associare a tale copia temporanea.

In entrambi i casi, tuttavia, lo standard richiede esplicitamente che: "Il costruttore che verrebbe utilizzato per eseguire la copia deve essere richiamabile indipendentemente dal fatto che la copia venga effettivamente eseguita.. "

Come tale, ho credo che gcc è giusto dare un messaggio di errore, e gli altri sono tecnicamente sbagliato ad accettare il codice ho semplificato il codice un po 'per la seguente:

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

void plop(N const& data) { } 

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

quando viene invocato con "--Una" (modalità rigorosa errori), Comeau dà il seguente messaggio di errore:

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

Allo stesso modo, quando viene invocato con "/ Za" (il suo "ANSI conforme" mode), VC++ 9 dà:

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 &' 

La mia ipotesi è che la maggior parte degli altri compilatori facciano la stessa cosa. Poiché ottimizzano la chiamata al costruttore di copie, di solito non richiedono che esista o siano accessibili. Quando chiedi loro di conformarsi allo standard il più accuratamente possibile, danno il messaggio di errore, perché è tecnicamente necessario anche se non lo usano.

+0

Esiste effettivamente una copia fatta qui 'plop (N (1));'? Se c'è non lo vedo perché al compilatore è permesso legare il riferimento direttamente all'oggetto che rappresenta il valore. La parte OR della conversazione non è quindi necessaria e quindi non è richiesta la copia. Sfortunatamente è il prossimo paragrafo (che costringe un compilatore conforme a produrre un errore) che mi ferisce. –

+0

@ Martin: Non riesco a immaginare un compilatore che potrebbe davvero fare una copia, anche con il suo livello minimo di ottimizzazione (anche se ovviamente * è * appena possibile). Il rivestimento d'argento è che la maggior parte dei compilatori (vale a dire sensibili) normalmente non la applicano, e con C++ 0x, il requisito sparirà. –

Problemi correlati