Come funziona l'elisione della copia garantita?


89

In occasione della riunione 2016 degli standard ISO C ++ di Oulu, una proposta denominata Eliminazione della copia garantita tramite categorie di valori semplificate è stata votata in C ++ 17 dal comitato degli standard.

Come funziona esattamente l'elisione della copia garantita? Copre alcuni casi in cui l'elisione della copia era già consentita o sono necessarie modifiche al codice per garantire l'elisione della copia?

Risposte:


129

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 Tdeve 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 tcon il valore restituito di Func. Tuttavia, Tdeve 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(), tviene 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_guardnon 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 FactoryFunctionrestituisce Tper 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, tdeve 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).


1
@BenVoigt: Mettere tipi definiti dall'utente non banalmente copiabili nei registri non è una cosa fattibile che un ABI può fare, indipendentemente dal fatto che l'elisione sia disponibile o meno.
Nicol Bolas

1
Ora che le regole sono pubbliche, può valere la pena aggiornarle con il concetto "prvalues ​​are initializations".
Johannes Schaub - litb

6
@ JohannesSchaub-litb: è "ambiguo" solo se si sa fin troppo sulle minuzie dello standard C ++. Per il 99% della comunità C ++, sappiamo a cosa si riferisce "l'elisione della copia garantita". L'attuale documento che propone il lungometraggio è addirittura intitolato "Guaranteed Copy Elision". L'aggiunta di "categorie di valori semplificate" rende semplicemente confuso e difficile da capire per gli utenti. Inoltre è un termine improprio, poiché queste regole non "semplificano" realmente le regole sulle categorie di valori. Che ti piaccia o no, il termine "eliminazione della copia garantita" si riferisce a questa funzione e nient'altro.
Nicol Bolas

1
Voglio così tanto essere in grado di prendere un prvalue e portarlo in giro. Immagino che questo sia solo un (one-shot) std::function<T()>davvero.
Yakk - Adam Nevraumont

1
@LukasSalich: Questa è una domanda C ++ 11. Questa risposta riguarda una funzionalità C ++ 17.
Nicol Bolas
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.