Semantyka przesuwania niekoniecznie jest tak wielką poprawą, gdy shared_ptrzwracasz 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::vectorwypeł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_ptrjest 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 ifstreams 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 reservemiejsca w vectortak jesteśmy pewni nasi ifstreams 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 ifstreamobiekty vectorbezpoś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_ptrtylko 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ść.