push_back vs Situace_back

761

Jestem trochę zdezorientowany co do różnicy między push_backi emplace_back.

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

Ponieważ istnieje push_backprzeciążenie przyjmujące odwołanie do wartości, nie bardzo rozumiem, jaki jest cel emplace_back?

ronag
źródło
16
Zauważ, że (jak Thomas mówi poniżej), kod w pytaniu pochodzi z emulacji MS ++ w C ++ 0x, a nie w rzeczywistości C ++ 0x.
me22
5
Lepszym tekstem do przeczytania byłoby: open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf . N2642 jest w przeważającej mierze sformułowaniem Standardu; N2345 to praca wyjaśniająca i motywująca ten pomysł.
Alan
Zauważ, że nawet w MSVC10 istnieje template <class _Valty> void emplace_back(_Valty&& _Val)wersja, która przyjmuje uniwersalne odniesienie, które zapewnia doskonałe przekazywanie do explicitkonstruktorów jednoparumentowych .
joki,
Powiązane: Czy jest jakiś przypadek, w którym push_backlepiej jest emplace_back? Jedyny przypadek, jaki mogę wymyślić, to czy klasa mogłaby być w jakiś sposób możliwa do skopiowania ( T&operator=(constT&)), ale nie nadawała się do konstruowania ( T(constT&)), ale nie mogę wymyślić, dlaczego ktoś tego chciałby.
Ben

Odpowiedzi:

568

Oprócz tego, co powiedział odwiedzający:

Funkcja void emplace_back(Type&& _Val)zapewniana przez MSCV10 jest niezgodna i redundantna, ponieważ, jak zauważyłeś, jest ściśle równoważna z push_back(Type&& _Val).

Ale prawdziwa forma C ++ 0x emplace_backjest naprawdę przydatna void emplace_back(Args&&...):;

Zamiast brać value_typezmienną, bierze się listę argumentów, co oznacza, że ​​możesz teraz idealnie przekazywać argumenty i konstruować bezpośrednio obiekt w kontenerze bez żadnych tymczasowych.

Jest to przydatne, ponieważ bez względu na to, jak sprytne RVO i semantyczne ruchy przynoszą do stołu, wciąż istnieją skomplikowane przypadki, w których push_back prawdopodobnie tworzy niepotrzebne kopie (lub ruchy). Na przykład, za pomocą tradycyjnej insert()funkcji a std::map, musisz utworzyć tymczasowy, który zostanie następnie skopiowany do a std::pair<Key, Value>, który następnie zostanie skopiowany na mapę:

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

Dlaczego więc nie wdrożyli właściwej wersji Situace_back w MSVC? Właściwie to też dawno mnie zepsuło, więc zadałem to samo pytanie na blogu Visual C ++ . Oto odpowiedź Stephan T. Lavavej, oficjalny opiekun implementacji standardowej biblioteki Visual C ++ w firmie Microsoft.

P: Czy funkcje umiejscawiania beta 2 są teraz teraz tylko jakimś symbolem zastępczym?

Odp .: Jak zapewne wiesz, szablony variadic nie są zaimplementowane w VC10. Symulujemy je za pomocą maszyn preprocesora do takich rzeczy, jak make_shared<T>()krotka i nowe rzeczy w <functional>. Ta maszyna preprocesorowa jest stosunkowo trudna w użyciu i utrzymaniu. Ma to również znaczący wpływ na szybkość kompilacji, ponieważ musimy wielokrotnie dołączać podtytuły. Ze względu na połączenie naszych ograniczeń czasowych i problemów związanych z prędkością kompilacji nie symulowaliśmy szablonów variadic w naszych funkcjach lokalizacyjnych.

Kiedy szablony różnorodne są implementowane w kompilatorze, możesz oczekiwać, że skorzystamy z nich w bibliotekach, w tym w naszych funkcjach lokalizacyjnych. Bardzo poważnie podchodzimy do zgodności, ale niestety nie możemy zrobić wszystkiego naraz.

To zrozumiała decyzja. Każdy, kto choć raz próbował naśladować szablon variadic z okropnymi sztuczkami preprocesora, wie, jak obrzydliwe są te rzeczy.

Thomas Petit
źródło
101
Najważniejsze jest wyjaśnienie, że jest to problem MSVS10, a nie C ++. Dzięki.
me22
11
Wierzę, że twój ostatni wiersz kodu C ++ nie zadziała. pair<const int,Complicated>nie ma konstruktora, który przyjmuje int, inny int, podwójny i jako czwarty parametr ciąg. Możesz jednak bezpośrednio skonstruować ten obiekt pary za pomocą jego konstruktora częściowego. Oczywiście składnia będzie inna:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
sellibitze
3
Na szczęście szablony będą dostępne w wersji VS2013, teraz w wersji zapoznawczej.
Daniel Earwicker
11
czy tę odpowiedź należy zaktualizować, aby odzwierciedlić nowe zmiany w porównaniu z rokiem 2013?
becko
6
Jeśli używasz Visual Studio 2013 lub nowszego teraz , trzeba mieć poparcie dla „prawdziwy” emplace_backtak długo, jak to zostało wdrożone w Visual C ++ gdy dodano szablony o zmiennej liczbie argumentów: msdn.microsoft.com/en-us/library/hh567368. aspx
kayleeFrye_onDeck
200

emplace_backnie powinien przyjmować argumentu typu vector::value_type, ale zamiast tego argumenty variadic, które są przekazywane do konstruktora dołączonego elementu.

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

Możliwe jest przekazanie takiego, value_typektóry zostanie przesłany do konstruktora kopii.

Ponieważ przekazuje argumenty, oznacza to, że jeśli nie masz wartości, oznacza to, że kontener będzie przechowywać „skopiowaną” kopię, a nie kopię przeniesioną.

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

Ale powyższe powinno być identyczne z tym, co push_backrobi. Prawdopodobnie jest raczej przeznaczony do takich przypadków użycia, jak:

 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
gość
źródło
2
@David: ale potem masz szakres, czy to nie jest niebezpieczne?
Matthieu M.,
2
Nie jest niebezpieczne, jeśli nie planujesz już używać s ze względu na jego wartość. Przeniesienie nie powoduje, że s jest niepoprawny, ruch jedynie wykradnie przydzieloną pamięć wewnętrzną już wykonaną ws i pozostawi ją w stanie domyślnym (bez przydzielenia żądła), który po zniszczeniu będzie w porządku, jakbyś właśnie wpisał std :: string str;
David
4
@David: Nie jestem pewien, czy przeniesiony z obiektu jest wymagany do jakiegokolwiek użycia oprócz późniejszego zniszczenia.
Ben Voigt,
46
vec.emplace_back("Hello")zadziała, ponieważ const char*argument zostanie przekazany do stringkonstruktora. To jest sedno sprawy emplace_back.
Alexandre C.,
8
@BenVoigt: Przeniesiony obiekt musi być w poprawnym (ale nieokreślonym) stanie. Nie musi to jednak oznaczać, że możesz wykonać na nim dowolną operację. Zastanów się std::vector. Pusty std::vectorjest prawidłowym stanem, ale nie można do front()niego zadzwonić . Oznacza to, że można wywoływać dowolną funkcję, która nie ma warunków wstępnych (a destruktory nigdy nie mogą mieć warunków wstępnych).
David Stone,
96

Optymalizację dla emplace_backmożna przedstawić w następnym przykładzie.

Bo zostanie wywołany emplace_backkonstruktor A (int x_arg). A ponieważ push_back A (int x_arg)nazywa się pierwszy, a move A (A &&rhs)potem nazywa się.

Oczywiście konstruktor musi być oznaczony jako explicit, ale w obecnym przykładzie dobrze jest usunąć jawność.

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

wynik:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)
vadikrobot
źródło
21
+1 dla przykładu kodu, które wykaże, co faktycznie dzieje się podczas wywoływania emplace_backvs push_back.
Shawn
Przyszedłem tutaj, gdy zauważyłem, że mam kod, który wywołuje, v.emplace_back(x);gdzie x jest jawnie konstruowane przez ruch, ale tylko przez kopiowanie. Fakt, że emplace_backjest „niejawnie” jawny, sprawia, że ​​myślę, że prawdopodobnie powinienem przejść do funkcji dołączania push_back. Myśli?
Ben
Jeśli zadzwonisz a.emplace_backpo raz drugi, zostanie wywołany konstruktor ruchu!
X Æ A-12
8

emplace_backzgodna implementacja przekaże argumenty do vector<Object>::value_typekonstruktora po dodaniu do wektora. Pamiętam, że Visual Studio nie obsługiwał szablonów variadic, ale szablony variadic będą obsługiwane w Visual Studio 2013 RC, więc sądzę, że zostanie dodany zgodny podpis.

Dzięki emplace_back, jeśli przekazuje argumenty bezpośrednio do vector<Object>::value_typekonstruktora, nie trzeba typ być ruchomy lub copyable dla emplace_backfunkcji, ściśle mówiąc. W tym vector<NonCopyableNonMovableObject>przypadku nie jest to przydatne, ponieważ vector<Object>::value_type do powiększenia wymaga typu kopiowalnego lub ruchomego.

Należy jednak pamiętać, że może to być przydatne std::map<Key, NonCopyableNonMovableObject>, ponieważ po przydzieleniu wpisu na mapie nie trzeba go już przenosić ani kopiować, w przeciwieństwie do vector, co oznacza, że ​​można std::mapefektywnie używać z typem odwzorowania , którego nie można skopiować ani ruchomy.

Germán Diago
źródło
8

Jeszcze jedno w przypadku list:

// 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"});
Adam Kauffman
źródło
1

Szczególny przypadek użycia dla emplace_back : Jeśli chcesz utworzyć obiekt tymczasowy, który zostanie następnie wepchnięty do kontenera, użyj emplace_backzamiast push_back. Stworzy obiekt w miejscu w kontenerze.

Uwagi:

  1. push_backw powyższym przypadku utworzy obiekt tymczasowy i przeniesie go do kontenera. Jednak zastosowana konstrukcja w miejscu emplace_backbyłaby bardziej wydajna niż konstruowanie, a następnie przenoszenie obiektu (co zwykle wymaga pewnego kopiowania).
  2. Ogólnie rzecz biorąc, możesz używać emplace_backzamiast push_backwe wszystkich przypadkach bez większego problemu. (Zobacz wyjątki )
Vaibhav Kumar
źródło