Funkcja C ++ 11 std::move(x)
tak naprawdę niczego nie przenosi. To tylko rzut na wartość r. Dlaczego to zrobiono? Czy to nie jest mylące?
c++
c++11
move-semantics
rvalue-reference
c++-faq
Howard Hinnant
źródło
źródło
std::move
std::char_traits::move
std::remove()
że nie usuwa elementów: nadal musisz wywołać,erase()
aby faktycznie usunąć te elementy z kontenera. Więcmove
nie rusza się,remove
nie usuwa. Wybrałbym nazwęmark_movable()
dlamove
.mark_movable()
mylący. Sugeruje, że istnieje trwały efekt uboczny, podczas gdy w rzeczywistości go nie ma.Odpowiedzi:
Prawdą jest, że
std::move(x)
jest to tylko rzutowanie na rwartość - a dokładniej na xwartość , w przeciwieństwie do prwartości . Prawdą jest również, że imię obsadymove
czasami dezorientuje ludzi. Jednak celem tego nazewnictwa nie jest zmylenie, ale raczej uczynienie kodu bardziej czytelnym.Historia
move
sięga do pierwotnej propozycji przeniesienia z 2002 roku . Ten artykuł najpierw wprowadza odniesienie do wartości r, a następnie pokazuje, jak napisać bardziej wydajnestd::swap
:Należy przypomnieć, że w tym momencie historii jedyną rzeczą, która
&&
mogłaby oznaczać, była logika i . Nikt nie był zaznajomiony z odniesieniami do rwartości, ani z implikacjami rzutowania lwartości na rwartość (nie robiąc kopii takstatic_cast<T>(t)
, jak by to zrobił). Czytelnicy tego kodu naturalnie pomyśleliby:Zauważ również, że
swap
jest to tak naprawdę tylko zastępstwo dla wszelkiego rodzaju algorytmów modyfikujących permutacje. Ta dyskusja jest dużo , dużo większa niżswap
.Następnie propozycja wprowadza cukier składniowy, który zastępuje
static_cast<T&&>
coś bardziej czytelnym, co nie wyjaśnia dokładnie, co , ale raczej dlaczego :To
move
znaczy jest po prostu cukierkiem składniowymstatic_cast<T&&>
, a teraz kod sugeruje, dlaczego istnieją te rzutowania: aby włączyć semantykę przenoszenia!Trzeba zrozumieć, że w kontekście historii niewiele osób w tym momencie naprawdę zrozumiało intymny związek między wartościami r i semantyką ruchu (choć artykuł próbuje to również wyjaśnić):
Jeśli w tamtym czasie
swap
został przedstawiony w ten sposób:Wtedy ludzie spojrzeliby na to i powiedzieli:
Główny punkt:
Tak jak było, używając
move
, nikt nigdy nie zapytał:W miarę upływu lat i udoskonalania propozycji, pojęcia lwartości i rwartości zostały dopracowane do kategorii wartości, które mamy dzisiaj:
(obraz bezwstydnie skradziony z dirkgently )
I tak dzisiaj, gdybyśmy chcieli
swap
precyzyjnie powiedzieć, co robi, zamiast dlaczego , powinien wyglądać bardziej tak:I pytanie, które każdy powinien sobie zadać, brzmi: czy powyższy kod jest mniej lub bardziej czytelny niż:
Albo nawet oryginał:
W każdym razie czeladnik programista C ++ powinien wiedzieć, że pod maską
move
nie dzieje się nic więcej niż obsada. A początkujący programista C ++, przynajmniej zmove
, zostanie poinformowany, że jego zamiarem jest przejście z prawej strony, a nie kopiowanie z prawej strony, nawet jeśli nie rozumieją dokładnie, jak to się robi .Dodatkowo, jeśli programista życzy sobie tej funkcjonalności pod inną nazwą,
std::move
nie ma monopolu na tę funkcjonalność i nie ma nieprzenośnej magii językowej związanej z jej implementacją. Na przykład, jeśli ktoś chciałby kodowaćset_value_category_to_xvalue
i zamiast tego używać tego, jest to trywialne:W C ++ 14 robi się jeszcze bardziej zwięźle:
Więc jeśli masz taką skłonność, udekoruj swój sposób, w
static_cast<T&&>
jaki myślisz najlepiej, a być może w końcu opracujesz nową najlepszą praktykę (C ++ stale się rozwija).Więc co robi
move
w zakresie wygenerowanego kodu obiektowego?Rozważ to
test
:Skompilowane za pomocą
clang++ -std=c++14 test.cpp -O3 -S
, generuje ten kod wynikowy:Teraz, jeśli test zostanie zmieniony na:
Nie ma absolutnie żadnej zmiany w kodzie wynikowym. Wynik ten można uogólnić tak, że: dla obiektów trywialnie poruszających się
std::move
nie ma żadnego wpływu.Spójrzmy teraz na ten przykład:
To generuje:
W przypadku uruchomienia
__ZN1XaSERKS_
przezc++filt
produkuje:X::operator=(X const&)
. Nic dziwnego. Teraz, jeśli test zostanie zmieniony na:Wtedy nadal nie ma żadnej zmiany w wygenerowanym kodzie obiektowym.
std::move
nie zrobił nic poza rzutowaniemj
na wartość r, a następnie ta wartość rX
wiąże się z operatorem przypisania kopiowania zX
.Teraz dodajmy operator przypisania przenoszenia do
X
:Teraz kod obiektowy się zmienia:
Przejście
__ZN1XaSEOS_
przezc++filt
objawienia, któreX::operator=(X&&)
są wywoływane zamiastX::operator=(X const&)
.I to wszystko
std::move
! Znika całkowicie w czasie wykonywania. Jego jedyny wpływ ma miejsce w czasie kompilacji, gdzie może zmienić wywołanie przeciążenia.źródło
digraph D { glvalue -> { lvalue; xvalue } rvalue -> { xvalue; prvalue } expression -> { glvalue; rvalue } }
dla dobra publicznego :) go pobrać tutaj jako SVGallow_move
;)movable
.std::move
narvalue_cast
: youtube.com/…rvalue_cast
jest niejednoznaczne w swoim znaczeniu: jaki rodzaj rwartości zwraca?xvalue_cast
byłoby tutaj spójną nazwą. Niestety, większość ludzi w tym czasie również nie rozumiałaby, co robi. Mam nadzieję, że za kilka lat moje oświadczenie stanie się fałszywe.Pozwólcie, że zostawię tutaj cytat z FAQ C ++ 11 napisanego przez B. Stroustrupa, który jest bezpośrednią odpowiedzią na pytanie OP:
Nawiasem mówiąc, bardzo podobały mi się FAQ - warto je przeczytać.
źródło