2015-03-09 13 views
9

Realizacja std::move zasadniczo wygląda następująco:Dlaczego polecenie std :: move pobiera odniesienie do przodu?

template<typename T> 
typename std::remove_reference<T>::type&& 
move(T&& t) 
{ 
    return static_cast<typename std::remove_reference<T>::type&&>(t); 
} 

Należy zauważyć, że parametr std::move jest uniwersalnym odniesienia (znany również jako odwołanie przekierowania, ale nie jesteśmy spedycja tutaj). Oznacza to, że można std::move zarówno lwartościami i rvalues:

std::string a, b, c; 
// ... 
foo(std::move(a));  // fine, a is an lvalue 
foo(std::move(b + c)); // nonsense, b + c is already an rvalue 

Ale ponieważ cały sens std::move jest do oddania do rvalue, dlaczego jesteśmy nawet dozwolony std::move rvalues? Czy nie miałoby większego sensu, gdyby std::move akceptował tylko wartości l?

template<typename T> 
T&& 
move(T& t) 
{ 
    return static_cast<T&&>(t); 
} 

Następnie bezsensowne wyrażenie std::move(b + c) spowodowałoby błąd podczas kompilacji.

Powyższa implementacja std::move byłaby również znacznie łatwiejsza do zrozumienia dla początkujących, ponieważ kod robi dokładnie to, co wydaje się robić: Zajmuje lwartość i zwraca wartość rurn. Nie musisz rozumieć uniwersalnych odniesień, referencyjnych zwijania i funkcji meta.

Dlaczego więc został std::move przeznaczony do przyjmowania zarówno wartości l, jak i wartości r?

+0

Moje przypuszczenie jest takie, że kiedy piszesz kod, aby poruszać rzeczy dookoła, nie chcesz ciągle myśleć, czy twoje zmienne to l-wartości, czy r-wartości, po prostu chcesz to zrobić. – sim642

+3

Ponieważ jest zaprojektowany do rzucania zarówno wartości bezwzględnej, jak i rwartościowej bezwarunkowo. –

+0

@ 6502 To pytanie lub jego szczegóły są bardziej przydatne dla programisty biblioteki niż dla programisty aplikacji. Programista aplikacji może po prostu zadzwonić do std :: move(); i zabrać się za pracę. Oczywiście należy zrozumieć, że przeniesienie zmiennej lokalnej spowodowałoby unieważnienie stanu. W przeciwnym razie nic nie złamie tutaj głowy. – Jagannath

Odpowiedz

9

Oto przykład uproszczony do skrajności:

#include <iostream> 
#include <vector> 

template<typename T> 
T&& my_move(T& t) 
{ 
    return static_cast<T&&>(t); 
} 

int main() 
{ 
    std::vector<bool> v{true}; 

    std::move(v[0]); // std::move on rvalue, OK 
    my_move(v[0]); // my_move on rvalue, OOPS 
} 

Przypadki takie jak ten powyżej, mogą pojawić się w ogólny kod, na przykład podczas korzystania z pojemników, które posiadają specjalizacje, które zwracają obiekty proxy (rvalues), a może nie wiesz, czy klient będzie korzystał ze specjalizacji, czy nie, więc chcesz bezwarunkowego wsparcia dla semantyki ruchu.

+3

Rewizja. Oto przykład z prawdziwego świata: https://github.com/llvm-mirror/libcxx/blob/master/include/algorithm#L2451 –

2

To nie boli.

Po prostu ustanawiasz gwarancję, że kod potraktuje wynik jako wartość rowności. Z pewnością możesz napisać std :: move w taki sposób, że błędnie, gdy masz do czynienia z czymś, co już jest rwartą, ale jaka jest korzyść?

W ogólnym kodzie, gdzie nie koniecznie wiesz, jakiego typu (typów) będziesz pracować, jakie zyski w ekspresywności wyciągnąłbyś z garści "jeśli typ jest rwalutą, nie rób niczego innego" :: poruszaj się "otynkowanym wszędzie, kiedy możesz po prostu powiedzieć" Obiecuję, że możemy myśleć o tym jako o wartości ".

Powiedziałeś to sam, to nic więcej niż gips. Czy operacje * _cast również nie powiodą się, jeśli argument jest już zgodny z zamierzonym typem?

+0

To ostatnie zdanie jest bardzo przekonujące. Dobra robota. +1 –

Powiązane problemy