2013-12-08 4 views
8

Avere un costruttore di copia pubblica farà compilare il piccolo programma , ma non mostrerà l'effetto collaterale "Copia".Perché è richiesto un costruttore di copie pubbliche anche se non è stato richiamato?

#include <iostream> 

class X 
{ 
    public: 
    X(int) { std::cout << "Construct" << std::endl; } 

    // Having a public copy constructor will make the little program 
    // compile, but not showing the side effect "Copy". 

    private: 
    X(const X&) { std::cout << "Copy" << std::endl; } 

    private: 
    X& operator = (const X&); 
}; 

int main() { 
    X x = 1; 
    return 0; 
} 
+2

X x = 1 significa X x (X (1)) per quanto ne so, ma è ottimizzato su X x (1); – odinthenerd

+3

È necessario in modo che il codice C++ sia trasferibile tra le implementazioni che possono o meno eseguire l'elisione della copia a propria discrezione. – jrok

+1

Prova a compilare con il flag '-fno-elide-constructors'. –

risposta

4

Ecco i corrispondenti bit dello standard C++ che sono coinvolte:

[dcl.init]/16, punto 6, sub-proiettile 1: Se l'inizializzazione inizializzazione diretta, o se è l'inizializzazione della copia in cui la versione cv -qualificata del tipo di origine è della stessa classe di, o una classe derivata di, la classe della destinazione, vengono considerati i costruttori .... Se nessun costruttore si applica o la risoluzione di sovraccarico è ambiguo, l'inizializzazione è mal formata. [enfasi aggiunta]

In altre parole, non importa se un'ottimizzazione del compilatore potrebbe elidere la copia, l'inizializzazione è mal formata perché non ci sono costruttori applicabili. Ovviamente, una volta reso pubblico il componente di copia copy:

[class.copy]/31: quando vengono soddisfatti determinati criteri, è consentita un'implementazione per omettere la costruzione di copia/spostamento di una classe oggetto, anche se il costruttore copia/sposta e/o il distruttore per l'oggetto hanno effetti collaterali .... Questa elisione delle operazioni di copia/spostamento, chiamata copia elisione, è consentita nelle seguenti circostanze (che possono essere combinate per eliminare più copie):

bullet 3: quando un oggetto di classe temporaneo che non è stato associato a un riferimento (12.2) viene copiato/spostato in un oggetto di classe con lo stesso tipo cv-non qualificato, l'operazione di copia/spostamento può essere omessa costruendo l'oggetto temporaneo d direttamente nella destinazione della copia/mossa omessa

2

La mia ipotesi migliore è che si tratta di un'ottimizzazione del compilatore. Esiste un percorso valido per dichiarare un oggetto in questo modo se si dispone di un costruttore di copie. Considerate fare questo in modo esplicito:

int main() { 
    X x(X(1)); 
    return 0; 
} 

O più esplicitamente:

int main() { 
    X intermediate(1); 
    X x(intermediate); 
    return 0; 
} 

Così, invece di usare operator=, si sa che si sta tentando di inizializzare l'oggetto in quanto la dichiarazione. In sostanza, il compilatore è "intelligente". Infine, ottimizza di nuovo a questo passo:

int main() { 
    X x(1); 
    return 0; 
} 

Pertanto, dopo il compilatore capisce "quello che stavi cercando di fare", questo diventa un standard di inizializzazione dell'oggetto.

EDIT

Per completezza, notare che se provate questo:

int main() { 
    X x = 1; 
    x = 2; 
    return 0; 
} 

si vedrà il problema con privata operator=. In modo esplicito, è importante notare che operator= non viene mai effettivamente utilizzato nella domanda di inizializzazione originale sopra sebbene = appaia nel codice.

+0

oops dovrebbe essere andato per la risposta piuttosto che commentare;) congratulazioni. – odinthenerd

+0

@PorkyBrain: Il tuo commento qui sopra si riferisce alla mia preoccupazione qui se 'X x (X (1))' è ottimizzato per 'X x (1)', grazie – RageD

+0

Il codice qui non usa affatto 'operator ='. 'X x = 1' è l'inizializzazione diretta di' x' e non implica un assegnamento; può (ma non è necessario) chiamare il costruttore di copie. –

2

È inizializzazione viene ottimizzata dal compilatore verso il basso per:

X x(1) 

Questa è una sorta di copia di elisione, ed è consentito dalla norma, anche se è possibile rimuovere gli effetti collaterali, come si è visto.

Dalla sezione standard C++ 03 12.8:

Quando determinati criteri sono soddisfatti, un'implementazione è permesso omettere la copia costruzione di un oggetto classe, anche se il costruttore di copia e/o il distruttore per l'oggetto ha effetti collaterali. In tali casi, l'implementazione considera l'origine e la destinazione dell'operazione omessa copia come due modi diversi di fare riferimento allo stesso oggetto e la distruzione di tale oggetto si verifica in un secondo momento delle volte quando i due gli oggetti sarebbero stati distrutti senza l'ottimizzazione . 111) Questa operazione di copiatura è consentita nelle seguenti circostanze (che possono essere combinate per eliminare più copie ): - in areturnstatement in una funzione con un tipo di ritorno classe, quando l'espressione è il nome di un non- oggetto automatico volatile con lo stesso tipo cv-non qualificato come tipo di ritorno della funzione, l'operazione di copia può essere omessa costruendo l'oggetto automatico direttamente nel valore di ritorno della funzione - quando un oggetto di classe temporanea che non è stato associato a un riferimento (12.2) sarebbero copiati in un oggetto di classe con lo stesso tipo cv-qualificato, l'operazione di copia può essere omesso costruendo l'oggetto tempo-Rary direttamente nella destinazione della copia omessa

Il secondo caso è quello che abbiamo qui.

+1

Non è un compito; è un'inizializzazione. –

8

È stata utilizzata la cosiddetta "inizializzazione della copia" (definita in [decl.init]). Il significato definito è di costruire un oggetto temporaneo di tipo X utilizzando il costruttore int e quindi di inizializzare x dal temporaneo utilizzando il costruttore di copie.

Tuttavia, lo standard consente anche un'ottimizzazione denominata "copia costruttore elisione" (definita in [class.copy]) in questo caso. Se viene applicata l'ottimizzazione, non vi è alcun limite. x viene costruito utilizzando il costruttore int proprio come se fosse stata scritta la cosiddetta "inizializzazione diretta" X x(1);.

Per non scrivere accidentalmente il codice che viene compilato quando l'ottimizzazione viene applicata ma non quando non lo è, lo standard richiede che il costruttore di copie debba essere accessibile anche se è elidato. Quindi, il costruttore deve essere pubblico anche se (con il compilatore e le opzioni che stai utilizzando) non viene chiamato.

In C++ sono considerati i costruttori di movimento C++, e sono eleggibili anche per elision. Tuttavia questa classe X non ha un costruttore di spostamento, quindi C++ 11 e C++ 03 sono gli stessi per questo esempio.

+2

In breve, la semantica impone una copia, e quindi il programma dovrebbe essere conforme alla semantica prima di essere ottimizzato. –