Wydajność C ++ 11 push_back () z std :: move versus embrace_back () dla już skonstruowanych obiektów

86

W C ++ 11 emplace_back()jest ogólnie preferowane (pod względem wydajności), push_back()ponieważ umożliwia konstruowanie w miejscu, ale czy nadal tak jest w przypadku używania push_back(std::move())z już zbudowanym obiektem?

Na przykład, czy emplace_back()nadal jest preferowany w takich przypadkach jak poniżej?

std::string mystring("hello world");
std::vector<std::string> myvector;

myvector.emplace_back(mystring);
myvector.push_back(std::move(mystring));
// (of course assuming we don't care about using the value of mystring after)

Dodatkowo, czy w powyższym przykładzie jest jakaś korzyść, aby zamiast tego zrobić:

myvector.emplace_back(std::move(mystring));

czy też ruch tutaj jest całkowicie zbędny, czy nie ma żadnego skutku?

Zamieszki
źródło
myvector.emplace_back(mystring);kopiuje i nie porusza się. Pozostałe dwa ruchy i powinny być równoważne.
TC

Odpowiedzi:

116

Zobaczmy, jak działają różne podane przez Ciebie wywołania:

  1. emplace_back(mystring): To jest konstrukcja lokalna nowego elementu z dowolnym podanym argumentem. Ponieważ podałeś lwartość, ta konstrukcja lokalna jest w rzeczywistości konstrukcją kopiującą, tj. Jest to to samo, co wywołaniepush_back(mystring)

  2. push_back(std::move(mystring)): To wywołuje metodę move-insertion, która w przypadku std :: string jest in-place move-construction.

  3. emplace_back(std::move(mystring)): To jest ponownie konstrukcja lokalna z podanymi przez Ciebie argumentami. Ponieważ argument ten jest wartością r, wywołuje konstruktor ruchu std::string, tj. Jest to konstrukcja ruchu w miejscu, taka jak w 2.

Innymi słowy, jeśli wywołane z jednym argumentem typu T, czy to rvalue, czy lvalue, emplace_backi push_backsą równoważne.

Jednak dla każdego innego argumentu emplace_backwygrywa wyścig, na przykład z char const*a vector<string>:

  1. emplace_back("foo")wzywa string::string(char const*)do budowy na miejscu.

  2. push_back("foo")najpierw musi wywołać string::string(char const*)niejawną konwersję potrzebną do dopasowania podpisu funkcji, a następnie wstawienie ruchu, jak w przypadku 2. powyżej. Dlatego jest równoważnepush_back(string("foo"))

Arne Mertz
źródło
1
Konstruktor przenoszenia jest ogólnie bardziej wydajny niż konstruktory kopiujące, dlatego użycie rvalue (przypadek 2, 3) jest bardziej wydajne niż użycie lwartości (przypadek 1) pomimo tej samej semantyki.
Ryan Li
co z taką sytuacją? void foo (string && s) {vector.emplace (s); // 1 wektor.emplace (std :: move (s)); // 2}
VALOD9
@VALOD to znowu numery 1 i 3 mojej listy. smogą być definiowane jako RValue odniesienia, które wiąże się tylko z rvalues, ale wewnątrz foo, sjest lwartością.
Arne Mertz
1

Emplace_back pobiera listę referencji rvalue i próbuje bezpośrednio skonstruować element kontenera. Możesz wywołać embrace_back ze wszystkimi typami obsługiwanymi przez konstruktory elementu kontenera. Kiedy wywołuje się embrace_back dla parametrów, które nie są referencjami rvalue, „powraca” do normalnych referencji i przynajmniej konstruktor kopiujący jest wywoływany, gdy parametr i elementy kontenera są tego samego typu. W twoim przypadku 'myvector.emplace_back (mystring)' powinien utworzyć kopię łańcucha, ponieważ kompilator nie mógł wiedzieć, że parametr myvector jest ruchomy. Więc wstaw std :: move, co daje pożądane korzyści. Push_back powinien działać tak samo dobrze jak embrace_back dla już skonstruowanych elementów.

Tunichtgut
źródło
2
To ... ciekawy sposób na opisanie spedycji / referencji uniwersalnych.
TC