unique_ptr<T>
nie zezwala na tworzenie kopii, zamiast tego obsługuje semantykę przenoszenia. Jednak mogę zwrócić a unique_ptr<T>
z funkcji i przypisać zwróconą wartość do zmiennej.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
Powyższy kod kompiluje się i działa zgodnie z przeznaczeniem. Dlaczego więc ta linia 1
nie wywołuje konstruktora kopiowania i nie powoduje błędów kompilatora? Gdybym musiał 2
zamiast tego użyć linii, miałoby to sens (używanie linii również 2
działa, ale nie jesteśmy do tego zobowiązani).
Wiem, że C ++ 0x zezwala na ten wyjątek, unique_ptr
ponieważ zwracana wartość jest obiektem tymczasowym, który zostanie zniszczony, gdy tylko funkcja wyjdzie, co gwarantuje unikalność zwracanego wskaźnika. Jestem ciekawy, jak to jest zaimplementowane, czy jest to specjalnie uwzględnione w kompilatorze, czy też istnieje specyfikacja językowa, którą wykorzystuje?
źródło
unique_ptr
. Całe pytanie dotyczy 1 i 2, które są dwoma różnymi sposobami osiągnięcia tego samego.main
wyjściu z funkcji, ale nie przyfoo
wyjściu.Odpowiedzi:
Tak, patrz 12.8 §34 i §35:
Chciałem tylko dodać jeszcze jeden punkt, że zwracanie wartości powinno być domyślnym wyborem, ponieważ w najgorszym przypadku nazwana wartość w instrukcji return, tj. Bez elekcji w C ++ 11, C ++ 14 i C ++ 17 jest traktowana jako wartość. Na przykład następująca funkcja kompiluje się z
-fno-elide-constructors
flagąPo ustawieniu flagi podczas kompilacji w tej funkcji występują dwa ruchy (1 i 2), a następnie jeden ruch później (3).
źródło
foo()
tak naprawdę wkrótce też zostanie zniszczony (jeśli nie zostałby do niczego przypisany), podobnie jak wartość zwracana w ramach funkcji, a zatem ma sens, że C ++ używa konstruktora ruchu podczas działaniaunique_ptr<int> p = foo();
?std::unique_ptr
), istnieje specjalna zasada, aby najpierw traktować obiekty jako wartości. Myślę, że zgadza się to całkowicie z odpowiedzią Nikoli.Nie jest to w żaden sposób specyficzne
std::unique_ptr
, ale dotyczy każdej klasy, która jest ruchoma. Gwarantują to reguły językowe, ponieważ wracasz wartościowo. Kompilator próbuje wymyślić kopie, wywołuje konstruktor przenoszenia, jeśli nie może usunąć kopii, wywołuje konstruktor kopii, jeśli nie może się poruszyć, i kompiluje się, jeśli nie może skopiować.Gdybyś miał funkcję, która przyjmuje
std::unique_ptr
jako argument, nie byłbyś w stanie przekazać do niej p. Będziesz musiał jawnie wywołać konstruktor ruchu, ale w tym przypadku nie powinieneś używać zmiennej p po wywołaniubar()
.źródło
p
nie jest to tymczasowe, wynikiem tegofoo()
, co jest zwracane, jest; dlatego jest to wartość i można ją przenosić, co umożliwia przypisaniemain
. Powiedziałbym, że się myliłeś, z tym wyjątkiem, że Nikola wydaje się stosować tę zasadę dop
siebie, co JEST błędne.1
a linią2
? Moim zdaniem jest to samo od kiedy budujep
sięmain
, to tylko dba o rodzaju typu powrotnejfoo
, prawda?Unique_ptr nie ma tradycyjnego konstruktora kopiowania. Zamiast tego ma „konstruktor ruchu”, który wykorzystuje odwołania do wartości:
Odwołanie do wartości (podwójny znak ampersand) będzie wiązało się tylko z wartością. Dlatego pojawia się błąd, gdy próbujesz przekazać wartość unikalna_ptr do funkcji. Z drugiej strony wartość zwracana z funkcji jest traktowana jak wartość, więc konstruktor ruchu jest wywoływany automatycznie.
Nawiasem mówiąc, będzie to działać poprawnie:
Tymczasowe unikalne_ptr tutaj jest wartością.
źródło
p
- „oczywiście” wartość - może być traktowana jako wartość w instrukcji returnreturn p;
w definicjifoo
. Nie sądzę, aby istniał problem z tym, że wartość zwracaną przez samą funkcję można „przenieść”.Myślę, że jest to doskonale wyjaśnione w punkcie 25 Effective Modern C ++ Scotta Meyersa . Oto fragment:
W tym przypadku RVO odnosi się do optymalizacji wartości zwracanej , a jeśli warunki dla RVO są spełnione, oznacza to zwrócenie lokalnego obiektu zadeklarowanego w funkcji, której można oczekiwać w RVO , co również jest ładnie wyjaśnione w punkcie 25 jego książki, odnosząc się do standard (tutaj obiekt lokalny obejmuje obiekty tymczasowe utworzone przez instrukcję return). Największym fragmentem fragmentu jest eliminacja kopii lub
std::move
jest ona domyślnie stosowana do zwracanych obiektów lokalnych . Scott wspomina w punkcie 25, którystd::move
jest domyślnie stosowany, gdy kompilator zdecyduje się nie pomijać kopii, a programista nie powinien tego wyraźnie robić.W twoim przypadku kod jest wyraźnie kandydatem do RVO, ponieważ zwraca obiekt lokalny,
p
a jego typp
jest taki sam, jak typ zwracany, co powoduje usunięcie kopii. A jeśli kompilator zdecyduje się nie ominąć kopii, z jakiegokolwiek powodu,std::move
wszedłby do linii1
.źródło
Jedną rzeczą, której nie widziałem w innych odpowiedziach, jestAby wyjaśnić inne odpowiedzi, że istnieje różnica między zwracaniem std :: unique_ptr, który został utworzony w funkcji, a tą, która została mu przekazana.Przykład może wyglądać tak:
źródło