Kopiowanie elizji było dozwolone w wielu okolicznościach. Jednak nawet jeśli było to dozwolone, kod nadal musiał działać tak, jakby kopia nie została usunięta. Mianowicie musiał istnieć dostępny konstruktor kopiowania i / lub przenoszenia.
Gwarantowana kopia elizja redefiniuje numer C ++ pojęć, takich, że pewne okoliczności, w których może być pomijana kopii / porusza się w rzeczywistości nie sprowokować Kopiuj / przenieś w ogóle . Kompilator nie usuwa kopii; norma mówi, że takie kopiowanie nigdy się nie wydarzy.
Rozważ tę funkcję:
T Func() {return T();}
Zgodnie z niegwarantowanymi regułami usuwania kopii, spowoduje to utworzenie tymczasowego, a następnie przeniesienie z tego tymczasowego do wartości zwracanej funkcji. Ta operacja przenoszenia może zostać usunięta, ale T
nadal musi mieć dostępny konstruktor przenoszenia, nawet jeśli nigdy nie jest używany.
Podobnie:
T t = Func();
To jest inicjalizacja kopii t
. Spowoduje to skopiowanie inicjalizacji t
z wartością zwracaną Func
. Jednak T
nadal musi mieć konstruktora przenoszenia, mimo że nie zostanie wywołany.
Gwarantowana eliminacja kopii na nowo definiuje znaczenie wyrażenia prvalue . Przed C ++ 17 prvalues są obiektami tymczasowymi. W C ++ 17 wyrażenie prvalue jest po prostu czymś, co może zmaterializować się tymczasowo, ale nie jest jeszcze tymczasowe.
Jeśli użyjesz prvalue do zainicjowania obiektu typu prvalue, wtedy żadna tymczasowa nie zostanie zmaterializowana. Kiedy to zrobisz return T();
, inicjalizuje wartość zwracaną funkcji przez prvalue. Ponieważ ta funkcja zwraca T
, nie jest tworzony obiekt tymczasowy; inicjalizacja wartości pr po prostu bezpośrednio inicjalizuje wartość zwracaną.
Należy zrozumieć, że skoro zwracana wartość jest wartością prvalue, nie jest jeszcze obiektem . Jest to jedynie inicjalizacja obiektu, tak jak T()
jest.
Gdy to zrobisz T t = Func();
, prvalue wartości zwracanej bezpośrednio inicjalizuje obiekt t
; nie ma etapu „utwórz tymczasowy i skopiuj / przenieś”. Ponieważ Func()
zwracana wartość jest odpowiednikiem prvalue T()
, t
jest inicjowana bezpośrednio przez T()
, dokładnie tak, jak gdybyś to zrobił T t = T()
.
Jeśli prvalue zostanie użyte w jakikolwiek inny sposób, prvalue zmaterializuje tymczasowy obiekt, który zostanie użyty w tym wyrażeniu (lub odrzucony, jeśli nie ma wyrażenia). Więc jeśli to zrobisz const T &rt = Func();
, prvalue zmaterializuje się tymczasowo (używając T()
jako inicjalizatora), którego odniesienie zostanie zapisane rt
, wraz ze zwykłymi tymczasowymi rozszerzeniami czasu życia.
Jedną z rzeczy, na które pozwala elision, jest zwracanie obiektów, które są nieruchome. Na przykład lock_guard
nie można go skopiować ani przenieść, więc nie można mieć funkcji, która zwraca go według wartości. Ale z gwarantowaną eliminacją kopii, możesz.
Gwarantowana elekcja działa również przy bezpośredniej inicjalizacji:
new T(FactoryFunction());
Jeśli FactoryFunction
zwraca T
wartość, to wyrażenie nie skopiuje wartości zwracanej do przydzielonej pamięci. Zamiast tego alokuje pamięć i używa przydzielonej pamięci jako pamięci wartości zwracanej bezpośrednio dla wywołania funkcji.
Dlatego funkcje fabryczne, które zwracają wartość, mogą bezpośrednio zainicjować przydzieloną pamięć sterty, nawet o tym nie wiedząc. Oczywiście pod warunkiem, że działają one wewnętrznie zgodnie z zasadami gwarantowanej eliminacji kopii. Muszą zwrócić prvalue typu T
.
Oczywiście to też działa:
new auto(FactoryFunction());
Jeśli nie lubisz pisać nazw typów.
Ważne jest, aby pamiętać, że powyższe gwarancje działają tylko dla wartości prvalues. Oznacza to, że nie masz gwarancji, że zwrócisz nazwaną zmienną:
T Func()
{
T t = ...;
...
return t;
}
W tym przypadku t
musi nadal mieć dostępny konstruktor kopiowania / przenoszenia. Tak, kompilator może zdecydować o optymalizacji kopiowania / przenoszenia. Jednak kompilator musi nadal zweryfikować istnienie dostępnego konstruktora kopiowania / przenoszenia.
Więc nic się nie zmienia dla nazwanej optymalizacji wartości zwracanej (NRVO).