2012-04-01 5 views
18

Nawiązując do komentarza zrobiłem w tej sprawie:miejscowi są zwracane automatycznie xvalues ​​

passing std::vector to constructor and move semantics Czy std::move konieczne w poniższym kodzie, aby upewnić się, że wartość zwracana jest xvalue?

std::vector<string> buildVector() 
{ 
    std::vector<string> local; 

    // .... build a vector 

    return std::move(local); 
} 

Rozumiem, że jest to wymagane. Nieraz widziałem to kiedyś po powrocie do std::unique_ptr z funkcji, jednak GManNickG wykonany następujący komentarz:

To jest moje zrozumienie, że w instrukcji return wszystkie zmienne lokalne są automatycznie xvalues ​​(upływającym wartości) i zostanie przeniesiony , ale nie jestem pewien, czy dotyczy to tylko zwróconego obiektu. Więc OP powinien iść dalej i umieścić to tam, dopóki nie będę bardziej pewny, że nie powinno tak być. :)

Czy ktoś może wyjaśnić, czy std::move jest konieczne?

Czy kompilator zachowania jest zależny?

+0

Uwaga pan kazał mi od zmodyfikować moje oświadczenie. Przesyłana jest tylko zwrócona wartość (która może być zmienną lokalną), a nie wszystkie zmienne lokalne. (Choć byłoby to miłe, prawdopodobnie łamie jakiś stary kod, którego nie mogę wymyślić, a progresja C++ musi zachować kompatybilność wsteczną.) – GManNickG

Odpowiedz

14

Masz gwarancję, że local zostanie zwrócony jako wartość r w tej sytuacji. Zwykle kompilatory przeprowadzałyby optymalizację wartości zwracanej, zanim jeszcze stanie się to problemem i prawdopodobnie nie zobaczyłbyś żadnego rzeczywistego ruchu, ponieważ obiekt local zostałby skonstruowany bezpośrednio na stronie wywołania.

Odpowiedni Uwaga w 6.6.3 [ „The return”] (2)

kopia lub przenosić działanie związane z informacją powrotnej może być pomijana lub uważane za rvalue w celu uzyskania rozdzielczości przeciążenia przy wyborze konstruktora (12.8).

Aby wyjaśnić, to znaczy, że zwracany obiekt może zostać przeniesiony z lokalnego obiektu (nawet jeśli w praktyce RVO całkowicie pominie ten krok). Normatywna część standardu to 12.8 ["Kopiowanie i przenoszenie obiektów klasy"] (31, 32), przy elizji kopiowania i wartościach rytmu (dzięki @Mankarse!).


Oto głupi przykład:

#include <utility> 

struct Foo 
{ 
    Foo()   = default; 
    Foo(Foo const &) = delete; 
    Foo(Foo &&)  = default; 
}; 

Foo f(Foo & x) 
{ 
    Foo y; 

    // return x;   // error: use of deleted function ‘Foo::Foo(const Foo&)’ 
    return std::move(x); // OK 
    return std::move(y); // OK 
    return y;   // OK (!!) 
} 

Kontrast to z powrotem rzeczywiste odniesienie rvalue:

Foo && g() 
{ 
    Foo y; 
    // return y;   // error: cannot bind ‘Foo’ lvalue to ‘Foo&&’ 
    return std::move(y); // OK type-wise (but undefined behaviour, thanks @GMNG) 
} 
+0

+1 (Dla jasności, ostatni ma UB i powinien być traktowany tylko jako przykład sprawdzający typ). – GManNickG

+0

@GManNickG: True; tak naprawdę nie zwracasz '&&' z niczego oprócz 'forward' i' move'. To wymyślny przykład. –

+0

Paragraf normatywny gwarantujący to zachowanie to "[klasa.kopii]/32". – Mankarse

4

Myślę, że odpowiedź brzmi: nie.Chociaż oficjalnie tylko notatki, §5/6 podsumowuje wyrażenia są/nie są xvalues:

Wyrażenie to xvalue jeśli jest to:

  • wynikiem wywołania funkcji, czy niejawnie lub jawnie, którego typem powrotu jest referencja wartości r względem typu obiektu, rzutowanie do referencji wartości raru do typu obiektu, wyrażenie dostępu do elementu klasy, określające niestatyczny element danych typu innego niż referencyjny, w którym Wyrażenie obiektu to xvalue, czyli
  • a. * Wyrażenie "wskaźnik do członka", w którym pierwszy operand jest wartością x, a drugi operand jest wskaźnikiem do elementu danych.

Ogólnie rzecz biorąc, efektem tej reguły jest to, że nazwane referencje rvalue są traktowane jako l-wartości, a nienazwane wartości rvalue dla obiektów są traktowane jako wartości x; Wartości rvalue dla funkcji są traktowane jako l-wartości, niezależnie od tego, czy są nazwane, czy nie.

Wydaje się, że dotyczy to pierwszy punktor. Ponieważ dana funkcja zwraca wartość zamiast wartości odniesienia rwart, wynik nie będzie wartością x.

+0

+1 za rozmowę o wartościach x, ponieważ też się myliłem. (Nie przypuszczam, żeby to był mój dzień.) Na szczęście, to, co naprawdę ważne, to to, czy wartość zwrotu może być traktowana jako wartość rubliczna, którą jest. Ale nie wszystkie wartości w ogóle, jak twierdziłem. – GManNickG

7

Mimo to, return std::move(local) i return local, działają w tym sensie, że się kompilują, ich zachowanie jest inne. I prawdopodobnie tylko ten ostatni był przeznaczony.

Jeśli napiszesz funkcję, która zwraca std::vector<string>, musisz zwrócić wartość std::vector<string> i dokładnie to. std::move(local) ma numer std::vector<string>&&, który jest nie a std::vector<string>, więc musi zostać przekonwertowany na niego za pomocą konstruktora ruchu.

Standardowe mówi 6.6.3.2:

Wartość ekspresji niejawnie przekształcany do typu zwracanej przez funkcję, w której występuje.

Oznacza to, return std::move(local) jest equalvalent do

std::vector<std::string> converted(std::move(local); // move constructor 
return converted; // not yet a copy constructor call (which will be elided anyway) 

natomiast return local tylko

return local; // not yet a copy constructor call (which will be elided anyway) 

To oszczędza ci jedną operację.


Aby dać krótki przykład, co to znaczy:

struct test { 
    test() { std::cout << " construct\n"; } 
    test(const test&) { std::cout << " copy\n"; } 
    test(test&&) { std::cout << " move\n"; } 
}; 

test f1() { test t; return t; } 
test f2() { test t; return std::move(t); } 

int main() 
{ 
    std::cout << "f1():\n"; test t1 = f1(); 
    std::cout << "f2():\n"; test t2 = f2(); 
} 

Będzie to wyjście

f1(): 
    construct 
f2(): 
    construct 
    move 
+0

Dzięki za twój wkład. – mark

+0

Należy zauważyć, że jest tak tylko w przypadku NVRO/elision. Bez konieczności "f1" również będzie musiał się poruszyć. –

+0

@NicolBolas: Tak, jest to część "jeszcze nie jest kopią konstruktora" napisana powyżej (cóż, właściwie musi to być wywołanie konstruktora ruchu). Ma to jednak wpływ zarówno na 'f1', jak i' f2', więc nawet wtedy 'f1' ma mniej jednego wywołania konstruktora. – ipc