Ho pensato a questa domanda un bel po 'negli ultimi quattro anni. Sono giunto alla conclusione che la maggior parte delle spiegazioni su push_back
vs. emplace_back
manca il quadro completo.
L'anno scorso ho tenuto una presentazione al C ++ Now on Type Deduction in C ++ 14 . Comincio a parlare push_back
contro emplace_back
alle 13:49, ma ci sono informazioni utili che forniscono alcune prove a supporto prima di quello.
La vera differenza principale ha a che fare con costruttori impliciti vs. espliciti. Considera il caso in cui abbiamo un singolo argomento a cui vogliamo passare push_back
o emplace_back
.
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
Dopo che il compilatore ottimizzatore ha messo le mani su questo, non c'è alcuna differenza tra queste due istruzioni in termini di codice generato. La saggezza tradizionale è che push_back
costruirà un oggetto temporaneo, nel quale verrà spostato, v
mentre emplace_back
avanzerà l'argomento e lo costruirà direttamente in posizione senza copie o mosse. Questo può essere vero in base al codice come scritto nelle librerie standard, ma fa l'ipotesi errata che il lavoro del compilatore di ottimizzazione sia quello di generare il codice che hai scritto. Il lavoro del compilatore di ottimizzazione è in realtà quello di generare il codice che avresti scritto se tu fossi un esperto di ottimizzazioni specifiche della piattaforma e non ti occupassi della manutenibilità, ma solo delle prestazioni.
La differenza effettiva tra queste due affermazioni è che più potente emplace_back
chiamerà qualsiasi tipo di costruttore là fuori, mentre più cauto push_back
chiamerà solo costruttori che sono impliciti. I costruttori impliciti dovrebbero essere sicuri. Se puoi implicitamente costruire un U
da un T
, stai dicendo che U
può contenere tutte le informazioni T
senza alcuna perdita. È sicuro in quasi ogni situazione passare un T
e nessuno si preoccuperà se lo fai U
invece. Un buon esempio di costruttore implicito è la conversione da std::uint32_t
a std::uint64_t
. Un cattivo esempio di una conversione implicita è double
a std::uint8_t
.
Vogliamo essere cauti nella nostra programmazione. Non vogliamo utilizzare funzionalità potenti perché più potente è la funzionalità, più facile è fare accidentalmente qualcosa di errato o imprevisto. Se hai intenzione di chiamare costruttori espliciti, allora hai bisogno del potere di emplace_back
. Se vuoi chiamare solo costruttori impliciti, mantieni la sicurezza di push_back
.
Un esempio
std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile
std::unique_ptr<T>
ha un costruttore esplicito da T *
. Poiché emplace_back
può chiamare costruttori espliciti, il passaggio di un puntatore non proprietario viene compilato correttamente. Tuttavia, quando v
esce dall'ambito, il distruttore tenterà di richiamare delete
quel puntatore, che non è stato allocato new
perché è solo un oggetto stack. Questo porta a un comportamento indefinito.
Questo non è solo un codice inventato. Questo è stato un vero errore di produzione che ho riscontrato. Il codice era std::vector<T *>
, ma possedeva il contenuto. Come parte della migrazione di C ++ 11, ho correttamente cambiato T *
per std::unique_ptr<T>
indicare che il vettore possedeva sua memoria. Tuttavia, mi è stato basando questi cambiamenti dalla mia comprensione nel 2012, durante il quale ho pensato "emplace_back fa di tutto push_back può fare di più, e allora perché mai dovrei usare push_back?", Così ho anche cambiato il push_back
a emplace_back
.
Se invece avessi lasciato il codice come usando il più sicuro push_back
, avrei immediatamente rilevato questo bug di vecchia data e sarebbe stato visto come un successo dell'aggiornamento a C ++ 11. Invece, ho mascherato il bug e non l'ho trovato fino a mesi dopo.