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, fktó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 fdostosowanych argumentów lvalue i rvalue. Aby zobaczyć, dlaczego tak jest, załóżmy, że fbierze się parametr wartości, gdzie Tjest jakiś możliwy do skopiowania i przeniesienia typ konstrukcyjny:
void f(T x) {
T y{std::move(x)};
}
Wywołanie fz argumentem lvalue spowoduje wywołanie konstruktora kopii w celu skonstruowania xi konstruktora ruchu w celu skonstruowania y. Z drugiej strony, wywołanie fz argumentem rvalue spowoduje wywołanie konstruktora ruchu w celu skonstruowania xi innego konstruktora ruchu w celu skonstruowania y.
Zasadniczo optymalna implementacja fargumentó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 fargumentó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ę, fktó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, fktó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, fktó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 fwymaga stałej referencji kosztuje tylko przypisanie zamiast przypisania ruchu. Mówiąc relatywnie, fpreferowana 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 fwypadek, gdyby zdecydowano się na przeciążenie kategorii wartości argumentu. Idealne przekazywanie ma tę wadę, że fstaje 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).