Semantyka przesuwania niekoniecznie jest tak wielką poprawą, gdy shared_ptr
zwracasz wartość - i kiedy / jeśli używasz (lub czegoś podobnego) prawdopodobnie przedwcześnie pesymujesz. W rzeczywistości prawie wszystkie racjonalnie nowoczesne kompilatory wykonują tak zwaną Optymalizację Wartości Zwrotnej (RVO) i Optymalizację Nazwanej Zwrotu Wartości (NRVO). Oznacza to, że kiedy wracasz wartość, zamiast faktycznie kopiowanie wartości w ogóle, po prostu przekazują ukryty wskaźnik / odniesienie do miejsca, w którym wartość zostanie przypisana po zwrocie, a funkcja używa tego do utworzenia wartości, w której ma się ona skończyć. Standard C ++ zawiera specjalne postanowienia, aby to umożliwić, więc nawet jeśli (na przykład) twój konstruktor kopiowania ma widoczne skutki uboczne, nie jest wymagane użycie konstruktora kopii do zwrócenia wartości. Na przykład:
#include <vector>
#include <numeric>
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <iterator>
class X {
std::vector<int> a;
public:
X() {
std::generate_n(std::back_inserter(a), 32767, ::rand);
}
X(X const &x) {
a = x.a;
std::cout << "Copy ctor invoked\n";
}
int sum() { return std::accumulate(a.begin(), a.end(), 0); }
};
X func() {
return X();
}
int main() {
X x = func();
std::cout << "sum = " << x.sum();
return 0;
};
Podstawowa idea tutaj jest dość prosta: stwórz klasę z wystarczającą ilością treści, wolelibyśmy unikać kopiowania, jeśli to możliwe ( std::vector
wypełnimy 32767 losowymi liczbami całkowitymi). Mamy wyraźny ctor kopiowania, który pokaże nam kiedy / jeśli zostanie skopiowany. Mamy też trochę więcej kodu do zrobienia czegoś z losowymi wartościami w obiekcie, więc optymalizator nie (przynajmniej łatwo) wyeliminuje wszystko w klasie tylko dlatego, że nic nie robi.
Następnie mamy kod, aby zwrócić jeden z tych obiektów z funkcji, a następnie użyć sumowania, aby upewnić się, że obiekt został naprawdę utworzony, a nie tylko całkowicie zignorowany. Kiedy go uruchamiamy, przynajmniej z najnowszymi / nowoczesnymi kompilatorami, okazuje się, że napisany przez nas konstruktor kopiowania nigdy nie działa - i tak, jestem prawie pewien, że nawet szybka kopia z poleceniem shared_ptr
jest nadal wolniejsza niż brak kopiowania w ogóle.
Przenoszenie pozwala ci robić wiele rzeczy, których po prostu nie możesz (bez nich) zrobić. Rozważ część „scalania” zewnętrznego typu scalania - masz, powiedzmy, 8 plików, które chcesz scalić razem. Idealnie chciałbyś umieścić wszystkie 8 tych plików w pliku vector
- ale ponieważ vector
(od C ++ 03) musi być w stanie kopiować elementy, a ifstream
s nie może być kopiowane, utkniesz z niektórymi unique_ptr
/shared_ptr
, lub coś w tej kolejności, aby móc umieścić je w wektorze. Należy pamiętać, że nawet jeśli (przykładowo) my reserve
miejsca w vector
tak jesteśmy pewni nasi ifstream
s będzie naprawdę nigdy nie mogą być kopiowane, kompilator nie będzie wiedział, że, więc kod nie będzie kompilować choć my wiemy, że konstruktor kopia nigdy nie będzie i tak używane.
Mimo że nadal nie można go skopiować, w C ++ 11 ifstream
można go przenieść. W tym przypadku obiekty prawdopodobnie nigdy nie zostaną przeniesione, ale fakt, że mogą być w razie potrzeby, sprawia, że kompilator jest szczęśliwy, dzięki czemu możemy umieścić nasze ifstream
obiekty vector
bezpośrednio, bez żadnych inteligentnych hacków wskaźnika.
Wektor, który się rozwija, to całkiem niezły przykład czasu, w którym semantyka ruchu naprawdę może być / jest użyteczna. W takim przypadku RVO / NRVO nie pomoże, ponieważ nie mamy do czynienia z wartością zwracaną z funkcji (lub czegokolwiek bardzo podobnego). Mamy jeden wektor zawierający niektóre obiekty i chcemy przenieść te obiekty do nowej, większej części pamięci.
W C ++ 03 dokonano tego, tworząc kopie obiektów w nowej pamięci, a następnie niszcząc stare obiekty w starej pamięci. Wykonywanie tych wszystkich kopii tylko po to, by wyrzucić stare, było jednak stratą czasu. W C ++ 11 można oczekiwać, że zostaną przeniesione. Zazwyczaj pozwala to nam zasadniczo wykonać płytką kopię zamiast (zwykle o wiele wolniejszej) głębokiej kopii. Innymi słowy, za pomocą łańcucha lub wektora (tylko dla kilku przykładów) po prostu kopiujemy wskaźnik (i) w obiektach, zamiast tworzyć kopie wszystkich danych, do których odnoszą się te wskaźniki.
shared_ptr
tylko ze względu na szybkie kopiowanie) i jeśli semantyka ruchu może osiągnąć to samo bez prawie żadnego karania za kodowanie, semantykę i czystość.