push_back vs emplace_back


762

Sono un po 'confuso riguardo alla differenza tra push_backe emplace_back.

void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);

Dato che c'è un push_backsovraccarico che prende un riferimento di valore, non vedo esattamente quale sia lo scopo di emplace_back?



16
Si noti che (come dice Thomas di seguito), il codice nella domanda proviene dall'emulazione di C ++ 0x di MSVS , non in realtà cosa sia C ++ 0x.
me22

5
Un documento migliore da leggere sarebbe: open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf . N2642 è principalmente formulato per lo standard; N2345 è il documento che spiega e motiva l'idea.
Alan,

Si noti che anche in MSVC10 esiste una template <class _Valty> void emplace_back(_Valty&& _Val)versione che accetta un riferimento universale che fornisce l'inoltro perfetto ai explicitcostruttori di argomenti singoli.
joki,

Correlati: esiste un caso in cui push_backè preferibile emplace_back? L'unico caso a cui riesco a pensare è se una classe fosse in qualche modo copiabile ( T&operator=(constT&)) ma non costruttibile ( T(constT&)), ma non riesco a pensare al motivo per cui uno lo vorrebbe mai.
Ben

Risposte:


569

Oltre a ciò che il visitatore ha detto:

La funzione void emplace_back(Type&& _Val)fornita da MSCV10 non è conforme e ridondante, perché come hai notato è strettamente equivalente a push_back(Type&& _Val).

Ma la vera forma C ++ 0x di emplace_backè davvero utile void emplace_back(Args&&...):;

Invece di prendere un value_typeprende un elenco variadico di argomenti, quindi ciò significa che ora puoi inoltrare perfettamente gli argomenti e costruire direttamente un oggetto in un contenitore senza un temporaneo.

Questo è utile perché non importa quanta intelligenza l'RVO e lo spostamento semantico portano al tavolo ci sono ancora casi complicati in cui è probabile che un push_back faccia copie (o spostamenti) non necessarie. Ad esempio, con la tradizionale insert()funzione di a std::map, devi creare un temporaneo, che verrà quindi copiato in a std::pair<Key, Value>, che verrà quindi copiato nella mappa:

std::map<int, Complicated> m;
int anInt = 4;
double aDouble = 5.0;
std::string aString = "C++";

// cross your finger so that the optimizer is really good
m.insert(std::make_pair(4, Complicated(anInt, aDouble, aString))); 

// should be easier for the optimizer
m.emplace(4, anInt, aDouble, aString);

Quindi perché non hanno implementato la versione corretta di emplace_back in MSVC? In realtà, mi ha infastidito troppo tempo fa, quindi ho posto la stessa domanda sul blog di Visual C ++ . Ecco la risposta di Stephan T Lavavej, manutentore ufficiale dell'implementazione della libreria standard Visual C ++ presso Microsoft.

D: Le funzioni di beta 2 emplace sono solo una specie di segnaposto in questo momento?

A: Come forse saprai, i modelli variadic non sono implementati in VC10. Li simuliamo con macchinari preprocessore per cose come make_shared<T>(), la tupla e le cose nuove <functional>. Questo macchinario preprocessore è relativamente difficile da usare e mantenere. Inoltre, influisce in modo significativo sulla velocità di compilazione, poiché dobbiamo includere ripetutamente i sottotitoli. A causa di una combinazione dei nostri vincoli temporali e problemi di velocità della compilazione, non abbiamo simulato modelli variadici nelle nostre funzioni di emplace.

Quando i modelli variadic sono implementati nel compilatore, puoi aspettarti che ne trarremo vantaggio nelle librerie, anche nelle nostre funzioni di emplace. Prendiamo molto sul serio la conformità, ma sfortunatamente non possiamo fare tutto in una volta.

È una decisione comprensibile. Chiunque abbia provato una sola volta a emulare il modello variadico con trucchi orribili del preprocessore sa quanto sia disgustosa questa roba.


101
Quel chiarimento che si tratta di un problema MSVS10, non di un problema C ++ è la parte più importante qui. Grazie.
me22

11
Credo che la tua ultima riga di codice C ++ non funzionerà. pair<const int,Complicated>non ha un costruttore che accetta un int, un altro int, un doppio e come quarto parametro una stringa. Tuttavia, è possibile realizzare direttamente questo oggetto paio utilizzando la sua tratti costruttore. La sintassi sarà diversa, ovviamente:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
sellibitze

3
I modelli fortunatamente variabili saranno in VS2013, ora in anteprima.
Daniel Earwicker,

11
questa risposta dovrebbe essere aggiornata per riflettere i nuovi sviluppi in vs2013?
becko,

6
Se si sta utilizzando Visual Studio 2013 o successivamente la società , si dovrebbe avere il supporto per il "vero" emplace_backfintanto che è stato implementato in Visual C ++, quando sono stati aggiunti i modelli variadic: msdn.microsoft.com/en-us/library/hh567368. aspx
kayleeFrye_onDeck il

200

emplace_backnon dovrebbe prendere un argomento di tipo vector::value_type, ma piuttosto argomenti vari che vengono inoltrati al costruttore dell'elemento aggiunto.

template <class... Args> void emplace_back(Args&&... args); 

È possibile passare un value_typeche verrà inoltrato al costruttore della copia.

Poiché inoltra gli argomenti, ciò significa che se non si dispone di rvalue, ciò significa che il contenitore memorizzerà una copia "copiata", non una copia spostata.

 std::vector<std::string> vec;
 vec.emplace_back(std::string("Hello")); // moves
 std::string s;
 vec.emplace_back(s); //copies

Ma quanto sopra dovrebbe essere identico a quello che push_backfa. Probabilmente è piuttosto pensato per casi d'uso come:

 std::vector<std::pair<std::string, std::string> > vec;
 vec.emplace_back(std::string("Hello"), std::string("world")); 
 // should end up invoking this constructor:
 //template<class U, class V> pair(U&& x, V&& y);
 //without making any copies of the strings

2
@ David: ma poi ti sei spostato snell'ambito, non è pericoloso?
Matthieu M.

2
Non è pericoloso se non prevedi di utilizzare s più per il suo valore. Lo spostamento non rende l'invalido, lo spostamento ruberà solo l'allocazione di memoria interna già eseguita in se lo lascerà in uno stato predefinito (nessuna allocazione di punti) che, se distrutta, andrà bene come se si fosse appena digitato std :: string str;
David

4
@David: non sono sicuro che un oggetto spostato da sia necessario per essere valido per qualsiasi uso tranne la distruzione successiva.
Ben Voigt,

46
vec.emplace_back("Hello")funzionerà, poiché l' const char*argomento verrà inoltrato al stringcostruttore. Questo è il punto emplace_back.
Alexandre C.

8
@BenVoigt: un oggetto spostato da deve essere in uno stato valido (ma non specificato). Questo non significa necessariamente che è possibile eseguire qualsiasi operazione su di esso, tuttavia. Prendere in considerazione std::vector. Uno spazio vuoto std::vectorè uno stato valido, ma non è possibile richiamarlo front(). Ciò significa che qualsiasi funzione che non ha precondizioni può ancora essere chiamata (e i distruttori non possono mai avere precondizioni).
David Stone,

96

L'ottimizzazione per emplace_backpuò essere dimostrata nel prossimo esempio.

Per il emplace_backcostruttore A (int x_arg)verrà chiamato. E perché push_back A (int x_arg)viene chiamato per primo e move A (A &&rhs)dopo viene chiamato.

Certo, il costruttore deve essere contrassegnato come explicit, ma per l'esempio attuale è bene rimuovere la spiegazione.

#include <iostream>
#include <vector>
class A
{
public:
  A (int x_arg) : x (x_arg) { std::cout << "A (x_arg)\n"; }
  A () { x = 0; std::cout << "A ()\n"; }
  A (const A &rhs) noexcept { x = rhs.x; std::cout << "A (A &)\n"; }
  A (A &&rhs) noexcept { x = rhs.x; std::cout << "A (A &&)\n"; }

private:
  int x;
};

int main ()
{
  {
    std::vector<A> a;
    std::cout << "call emplace_back:\n";
    a.emplace_back (0);
  }
  {
    std::vector<A> a;
    std::cout << "call push_back:\n";
    a.push_back (1);
  }
  return 0;
}

produzione:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)

21
+1 per l'esempio di codice che dimostra ciò che effettivamente accade quando si chiama emplace_backvs push_back.
Shawn,

Sono venuto qui dopo aver notato che avevo un codice che chiamava v.emplace_back(x);dove x è esplicitamente costruibile con mosse ma solo esplicitamente costruibile con copia. Il fatto che emplace_backsia "implicitamente" esplicito mi fa pensare che probabilmente dovrebbe essere la mia funzione go-to per aggiungere push_back. Pensieri?
Ben

Se chiami la a.emplace_backseconda volta, verrà chiamato il costruttore di mosse!
X Æ A-12,


8

emplace_backun'implementazione conforme inoltra gli argomenti al vector<Object>::value_typecostruttore quando viene aggiunta al vettore. Ricordo che Visual Studio non supportava i modelli variadic, ma con i modelli variadic sarà supportato in Visual Studio 2013 RC, quindi suppongo che verrà aggiunta una firma conforme.

Con emplace_back, se inoltri gli argomenti direttamente al vector<Object>::value_typecostruttore, non hai bisogno di un tipo per essere mobile o copiabile per la emplace_backfunzione, a rigor di termini. Nel vector<NonCopyableNonMovableObject>caso, questo non è utile, poiché ha vector<Object>::value_type bisogno di un tipo copiabile o mobile per crescere.

Ma nota che questo potrebbe essere utile std::map<Key, NonCopyableNonMovableObject>, poiché una volta allocata una voce nella mappa, non è più necessario spostarla o copiarla, a differenza di vector, il che significa che puoi usarla std::mapefficacemente con un tipo mappato che non è né copiabile né mobile.


8

Ancora uno in caso di liste:

// constructs the elements in place.                                                
emplace_back("element");


//It will create new object and then copy(or move) its value of arguments.
push_back(explicitDataType{"element"});

1

Caso d'uso specifico per emplace_back: se è necessario creare un oggetto temporaneo che verrà quindi inserito in un contenitore, utilizzare emplace_backinvece dipush_back . Creerà l'oggetto sul posto all'interno del contenitore.

Appunti:

  1. push_backnel caso precedente creerà un oggetto temporaneo e lo sposterà nel contenitore. Tuttavia, la costruzione in loco utilizzata emplace_backsarebbe più performante della costruzione e quindi dello spostamento dell'oggetto (che generalmente comporta alcune copie).
  2. In generale, è possibile utilizzare emplace_backanziché push_backin tutti i casi senza problemi. (Vedi eccezioni )
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.