push_back vs emplace_back

Ik ben een beetje in de war over het verschil tussen push_backen emplace_back.

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

Aangezien er een push_backoverbelasting is bij het nemen van een rvalue-referentie, begrijp ik niet helemaal wat het doel van emplace_backwordt?


Antwoord 1, autoriteit 100%

In aanvulling op wat de bezoeker zei:

De functie void emplace_back(Type&& _Val)geleverd door MSCV10 is niet-conform en overbodig, omdat het, zoals u opmerkte, strikt equivalent is aan push_back(Type&& _Val).

Maar de echte C++0x-vorm van emplace_backis erg handig: void emplace_back(Args&&...);

In plaats van een value_typete nemen, is er een variadische lijst met argumenten nodig, dus dat betekent dat je de argumenten nu perfect kunt doorsturen en direct een object in een container kunt construeren zonder enige tijdelijke.

Dat is handig, want hoeveel slimheid RVO en semantiek ook brengen, er zijn nog steeds gecompliceerde gevallen waarin een push_back waarschijnlijk onnodige kopieën (of verplaatsing) maakt. Met de traditionele functie insert()van een std::mapmoet u bijvoorbeeld een tijdelijk bestand maken, dat vervolgens wordt gekopieerd naar een std::pair<Key, Value>, die vervolgens naar de kaart wordt gekopieerd :

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);

Dus waarom hebben ze niet de juiste versie van emplace_back in MSVC geïmplementeerd? Eigenlijk zat ik er een tijdje geleden ook mee, dus ik stelde dezelfde vraag op de Visual C++ blog. Hier is het antwoord van Stephan T Lavavej, de officiële beheerder van de Visual C++ standaard bibliotheekimplementatie bij Microsoft.

V: Zijn bèta 2-emplace-functies op dit moment slechts een soort tijdelijke aanduiding?

A: Zoals u wellicht weet, variadische sjablonen
zijn niet geïmplementeerd in VC10. We
simuleer ze met preprocessor
machines voor dingen als:
make_shared<T>(), tuple en de nieuwe
dingen in <functional>. Dit
preprocessormachines zijn relatief
moeilijk in gebruik en onderhoud. Ook,
het heeft een aanzienlijke invloed op de compilatie
snelheid, zoals we herhaaldelijk moeten doen
ondertitels opnemen. Door een
combinatie van onze tijdsdruk
en compilatiesnelheid betreft, we
hebben geen variadische sjablonen gesimuleerd
in onze emplace-functies.

Als variadische sjablonen zijn
geïmplementeerd in de compiler, kunt u:
verwachten dat we zullen profiteren van
ze in de bibliotheken, ook in
onze emplace-functies. We nemen
conformiteit zeer serieus, maar
helaas kunnen we niet alles doen
allemaal tegelijk.

Het is een begrijpelijke beslissing. Iedereen die slechts één keer heeft geprobeerd om een ​​variadische sjabloon na te bootsen met vreselijke preprocessor-trucs, weet hoe walgelijk dit spul wordt.


Antwoord 2, autoriteit 33%

emplace_backmag geen argument van het type vector::value_typeaannemen, maar in plaats daarvan variadische argumenten die worden doorgestuurd naar de constructor van het toegevoegde item.

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

Het is mogelijk om een ​​value_typedoor te geven die wordt doorgestuurd naar de kopieerconstructor.

Omdat het de argumenten doorstuurt, betekent dit dat als je geen rvalue hebt, dit nog steeds betekent dat de container een “gekopieerde” kopie opslaat, geen verplaatste kopie.

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

Maar het bovenstaande moet identiek zijn aan wat push_backdoet. Het is waarschijnlijk eerder bedoeld voor gebruikssituaties zoals:

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

Antwoord 3, autoriteit 17%

Optimalisatie voor emplace_backkan in het volgende voorbeeld worden gedemonstreerd.

Voor emplace_backwordt de constructor A (int x_arg)aangeroepen. En voor
push_backA (int x_arg)wordt eerst aangeroepen en move A (A &&rhs)wordt daarna aangeroepen.

Natuurlijk moet de constructor worden gemarkeerd als explicit, maar voor het huidige voorbeeld is het goed om de explicietheid te verwijderen.

#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;
}

uitvoer:

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

Antwoord 4, autoriteit 2%

Nog een voorbeeld voor lijsten:

// constructs the elements in place.                                                
emplace_back("element");
// creates a new object and then copies (or moves) that object.
push_back(ExplicitDataType{"element"});

Antwoord 5

Een mooie code voor de push_back en emplace_back wordt hier getoond.

http://en.cppreference.com/w/cpp/container/ vector/emplace_back

Je kunt de verplaatsingsbewerking zien op push_back en niet op emplace_back.


Antwoord 6

emplace_back-conforme implementatie zal argumenten doorsturen naar de vector<Object>::value_typeconstructor wanneer deze aan de vector wordt toegevoegd. Ik herinner me dat Visual Studio geen variadische sjablonen ondersteunde, maar dat variadische sjablonen worden ondersteund in Visual Studio 2013 RC, dus ik denk dat er een overeenkomstige handtekening zal worden toegevoegd.

Met emplace_back, als u de argumenten rechtstreeks doorstuurt naar de vector<Object>::value_typeconstructor, heeft u geen type nodig om verplaatsbaar of kopieerbaar te zijn voor emplace_backfunctie, strikt genomen. In het geval vector<NonCopyableNonMovableObject>is dit niet nuttig, aangezien vector<Object>::value_typeeen kopieerbaar of verplaatsbaar type nodig heeft om te groeien.

Maar houd er rekening meedat dit nuttig kan zijn voor std::map<Key, NonCopyableNonMovableObject>, aangezien als u eenmaal een item op de kaart hebt toegewezen, dit niet nodig is nooit meer worden verplaatst of gekopieerd, in tegenstelling tot vector, wat betekent dat u std::mapeffectief kunt gebruiken met een toegewezen type dat noch kopieerbaar noch verplaatsbaar is.


Antwoord 7

Specifieke use case voor emplace_back: als u een tijdelijk object moet maken dat vervolgens in een container wordt gepusht, gebruikt u emplace_backin plaats van push_back. Het maakt het object op zijn plaats in de container.

Opmerkingen:

  1. push_backzal in het bovenstaande geval een tijdelijk object maken en verplaatsen
    in de container. Echter, in-place constructie gebruikt voor emplace_backzou meer zijn
    performanter dan het construeren en vervolgens verplaatsen van het object (wat over het algemeen wat kopiëren met zich meebrengt).
  2. Over het algemeen kun je in alle gevallen zonder veel problemen emplace_backgebruiken in plaats van push_back. (Zie uitzonderingen)

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Other episodes