L'elisione della copia era consentita in una serie di circostanze. Tuttavia, anche se fosse consentito, il codice doveva comunque essere in grado di funzionare come se la copia non fosse stata elisa. Vale a dire, doveva esserci un costruttore di copia e / o spostamento accessibile.
Garantito elision copia ridefinisce una serie di concetti C ++, in modo tale che alcune circostanze in cui potrebbero essere eliso copie / si muove in realtà non provocano un copia / spostamento a tutti . Il compilatore non elide una copia; lo standard dice che nessuna copia di questo tipo potrebbe mai avvenire.
Considera questa funzione:
T Func() {return T();}
In base alle regole di elisione della copia non garantita, questo creerà un temporaneo, quindi si sposterà da quel temporaneo al valore di ritorno della funzione. Quell'operazione di spostamento può essere elisa, ma T
deve comunque avere un costruttore di mosse accessibile anche se non viene mai utilizzata.
Allo stesso modo:
T t = Func();
Questa è l'inizializzazione della copia di t
. Questo copierà l'inizializzazione t
con il valore restituito di Func
. Tuttavia, T
deve ancora avere un costruttore di mosse, anche se non verrà chiamato.
L'elisione della copia garantita ridefinisce il significato di un'espressione prvalue . Prima di C ++ 17, i prvalues sono oggetti temporanei. In C ++ 17, un'espressione prvalue è semplicemente qualcosa che può materializzare una temporanea, ma non è ancora una temporanea.
Se si utilizza un prvalue per inizializzare un oggetto del tipo prvalue, non viene materializzato alcun temporaneo. Quando lo fai return T();
, questo inizializza il valore di ritorno della funzione tramite un prvalue. Poiché quella funzione ritorna T
, non viene creato alcun temporaneo; l'inizializzazione del prvalue inizia semplicemente direttamente il valore di ritorno.
La cosa da capire è che, poiché il valore restituito è un prvalue, non è ancora un oggetto . È semplicemente un inizializzatore di un oggetto, proprio come lo T()
è.
Quando lo fai T t = Func();
, il prvalue del valore restituito inizializza direttamente l'oggetto t
; non esiste una fase "crea una temporanea e copia / sposta". Poiché Func()
il valore di ritorno di è un prvalue equivalente a T()
, t
viene inizializzato direttamente da T()
, esattamente come se lo avessi fatto T t = T()
.
Se un prvalue viene utilizzato in qualsiasi altro modo, il prvalue materializzerà un oggetto temporaneo, che verrà utilizzato in quell'espressione (o scartato se non ci sono espressioni). Quindi, se lo facessi const T &rt = Func();
, il prvalue materializzerebbe un temporaneo (usando T()
come inizializzatore), il cui riferimento verrebbe memorizzato rt
, insieme alle solite cose temporanee di estensione della durata.
Una cosa garantita che l'elisione ti permette di fare è restituire gli oggetti che sono immobili. Ad esempio, lock_guard
non può essere copiato o spostato, quindi non potresti avere una funzione che lo restituisca per valore. Ma con l'elisione della copia garantita, puoi.
L'elisione garantita funziona anche con l'inizializzazione diretta:
new T(FactoryFunction());
Se FactoryFunction
restituisce T
per valore, questa espressione non copierà il valore restituito nella memoria allocata. Invece allocherà la memoria e utilizzerà la memoria allocata come memoria del valore di ritorno per la chiamata di funzione direttamente.
Quindi le funzioni di fabbrica che restituiscono per valore possono inizializzare direttamente la memoria allocata nell'heap senza nemmeno saperlo. Fintanto che queste funzioni seguono internamente le regole dell'elisione della copia garantita, ovviamente. Devono restituire un prvalue di tipo T
.
Ovviamente funziona anche questo:
new auto(FactoryFunction());
Nel caso in cui non ti piaccia scrivere nomi di tipo.
È importante riconoscere che le garanzie di cui sopra funzionano solo per i prvalori. Cioè, non ottieni alcuna garanzia quando restituisci una variabile denominata :
T Func()
{
T t = ...;
...
return t;
}
In questo caso, t
deve ancora avere un costruttore di copia / spostamento accessibile. Sì, il compilatore può scegliere di ottimizzare la copia / spostamento. Ma il compilatore deve comunque verificare l'esistenza di un costruttore di copia / spostamento accessibile.
Quindi non cambia nulla per l'ottimizzazione del valore di ritorno denominato (NRVO).