Jak zauważył @ JDługosz w komentarzach, Herb udziela innych rad w innym (później?) Przemówieniu, patrz mniej więcej stąd: https://youtu.be/xnqTKD8uD64?t=54m50s .
Jego rada sprowadza się tylko do używania parametrów wartości dla funkcji, f
która pobiera tak zwane argumenty sink, zakładając, że przeniesiesz konstrukcję z tych argumentów sink.
To ogólne podejście dodaje tylko narzut konstruktora ruchu zarówno dla argumentów lvalue, jak i rvalue w porównaniu z optymalną implementacją odpowiednio f
dostosowanych argumentów lvalue i rvalue. Aby zobaczyć, dlaczego tak jest, załóżmy, że f
bierze się parametr wartości, gdzie T
jest jakiś możliwy do skopiowania i przeniesienia typ konstrukcyjny:
void f(T x) {
T y{std::move(x)};
}
Wywołanie f
z argumentem lvalue spowoduje wywołanie konstruktora kopii w celu skonstruowania x
i konstruktora ruchu w celu skonstruowania y
. Z drugiej strony, wywołanie f
z argumentem rvalue spowoduje wywołanie konstruktora ruchu w celu skonstruowania x
i innego konstruktora ruchu w celu skonstruowania y
.
Zasadniczo optymalna implementacja f
argumentów lvalue jest następująca:
void f(const T& x) {
T y{x};
}
W takim przypadku do konstruowania wywoływany jest tylko jeden konstruktor kopii y
. Optymalna implementacja f
argumentów dla wartości jest znowu ogólnie następująca:
void f(T&& x) {
T y{std::move(x)};
}
W tym przypadku do konstruowania wywoływany jest tylko jeden konstruktor ruchu y
.
Rozsądnym kompromisem jest więc przyjęcie parametru wartości i wywołanie dodatkowego konstruktora ruchu dla argumentów lvalue lub rvalue w odniesieniu do optymalnej implementacji, co jest również wskazówką zawartą w wykładzie Herb.
Jak zauważył @ JDługosz w komentarzach, przekazywanie przez wartość ma sens tylko dla funkcji, które zbudują jakiś obiekt z argumentu sink. Jeśli masz funkcję, f
która kopiuje swój argument, podejście polegające na przekazywaniu przez wartość będzie miało większy narzut niż ogólne podejście polegające na przekazywaniu przez stałą. Metoda przekazywania wartości dla funkcji, f
która zachowuje kopię swojego parametru, będzie miała postać:
void f(T x) {
T y{...};
...
y = std::move(x);
}
W takim przypadku istnieje konstrukcja kopiowania i przypisanie ruchu dla argumentu wartości, a także przeniesienie budowy i przypisanie ruchu dla argumentu wartości. Najbardziej optymalnym przypadkiem argumentu lvalue jest:
void f(const T& x) {
T y{...};
...
y = x;
}
Sprowadza się to tylko do przypisania, które jest potencjalnie znacznie tańsze niż konstruktor kopiowania plus przypisanie do ruchu wymagane w podejściu typu pass-by-value. Powodem tego jest to, że przypisanie może ponownie wykorzystać istniejącą przydzieloną pamięć y
, a tym samym zapobiegać (de) przydziałom, podczas gdy konstruktor kopiowania zwykle przydziela pamięć.
Dla argumentu wartości najbardziej optymalna implementacja, f
która zachowuje kopię, ma postać:
void f(T&& x) {
T y{...};
...
y = std::move(x);
}
Zatem w tym przypadku tylko przypisanie ruchu. Przekazanie wartości do tej wersji f
wymaga stałej referencji kosztuje tylko przypisanie zamiast przypisania ruchu. Mówiąc relatywnie, f
preferowana jest wersja przyjmowania stałego odniesienia w tym przypadku jako ogólnej implementacji.
Ogólnie rzecz biorąc, aby uzyskać najbardziej optymalną implementację, konieczne będzie przeładowanie lub wykonanie pewnego rodzaju doskonałego przekazywania, jak pokazano w wykładzie. Wadą jest kombinatoryczna eksplozja w liczbie wymaganych przeciążeń, w zależności od liczby parametrów, na f
wypadek, gdyby zdecydowano się na przeciążenie kategorii wartości argumentu. Idealne przekazywanie ma tę wadę, że f
staje się funkcją szablonu, która zapobiega wirtualizacji i skutkuje znacznie bardziej złożonym kodem, jeśli chcesz uzyskać go w 100% poprawnie (zobacz szczegóły dotyczące krwawych szczegółów).