Ho pensato a questa domanda un bel po 'negli ultimi quattro anni. Sono giunto alla conclusione che la maggior parte delle spiegazioni su push_backvs. emplace_backmanca il quadro completo.
L'anno scorso ho tenuto una presentazione al C ++ Now on Type Deduction in C ++ 14 . Comincio a parlare push_backcontro emplace_backalle 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_backo 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_backcostruirà un oggetto temporaneo, nel quale verrà spostato, vmentre emplace_backavanzerà 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_backchiamerà qualsiasi tipo di costruttore là fuori, mentre più cauto push_backchiamerà solo costruttori che sono impliciti. I costruttori impliciti dovrebbero essere sicuri. Se puoi implicitamente costruire un Uda un T, stai dicendo che Upuò contenere tutte le informazioni Tsenza alcuna perdita. È sicuro in quasi ogni situazione passare un Te nessuno si preoccuperà se lo fai Uinvece. Un buon esempio di costruttore implicito è la conversione da std::uint32_ta std::uint64_t. Un cattivo esempio di una conversione implicita è doublea 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_backpuò chiamare costruttori espliciti, il passaggio di un puntatore non proprietario viene compilato correttamente. Tuttavia, quando vesce dall'ambito, il distruttore tenterà di richiamare deletequel puntatore, che non è stato allocato newperché è 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_backa 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.