W C ++ 11 możemy napisać taki kod:
struct Cat {
Cat(){}
};
const Cat cat;
std::move(cat); //this is valid in C++11
kiedy dzwonię std::move
, to znaczy, że chcę przesunąć obiekt, czyli zmienię obiekt. Przesuwanie const
obiektu jest nierozsądne, więc dlaczego std::move
nie ogranicza tego zachowania? To będzie pułapka w przyszłości, prawda?
Tutaj pułapka oznacza, jak wspomniał Brandon w komentarzu:
"Myślę, że ma na myśli to, że" więzi "go podstępnie podstępnie, ponieważ jeśli nie zdaje sobie sprawy, kończy się z kopią, która nie jest tym, co zamierzał."
W książce „Effective Modern C ++” Scotta Meyersa podaje przykład:
class Annotation {
public:
explicit Annotation(const std::string text)
: value(std::move(text)) //here we want to call string(string&&),
//but because text is const,
//the return type of std::move(text) is const std::string&&
//so we actually called string(const string&)
//it is a bug which is very hard to find out
private:
std::string value;
};
Gdyby std::move
zabroniono nam operowania na const
obiekcie, moglibyśmy łatwo znaleźć błąd, prawda?
std::move
sam w sobie nie robi nic na przedmiot. Można by argumentować, żestd::move
jest źle nazwany.CAT cat2 = std::move(cat);
, zakładając, żeCAT
obsługuje regularne przypisywanie ruchów.std::move
to tylko obsada, tak naprawdę niczego nie ruszaOdpowiedzi:
tutaj widzimy użycie
std::move
naT const
. Zwraca aT const&&
. Mamy konstruktora przenoszenia,strange
który przyjmuje dokładnie ten typ.I to się nazywa.
To prawda, że ten dziwny typ jest rzadszy niż błędy, które naprawiłaby twoja propozycja.
Ale z drugiej strony istniejący
std::move
działa lepiej w kodzie ogólnym, w którym nie wiesz, czy typ, z którym pracujesz, to aT
czy aT const
.źródło
std::move
naconst
obiekcie.const T&&
. Wyraża to „protokół API” w rodzaju „wezmę rvalue-ref, ale obiecuję, że go nie zmodyfikuję”. Myślę, że poza używaniem mutable jest to rzadkie. Może innym przypadkiem użycia jest możliwość użyciaforward_as_tuple
na prawie wszystkim, a później użycia tego.Jest tu pewna sztuczka, którą przeoczasz, a mianowicie, że
std::move(cat)
tak naprawdę niczego nie rusza . Po prostu mówi kompilatorowi, aby spróbował się przenieść. Jednak ponieważ twoja klasa nie ma konstruktora, który akceptuje aconst CAT&&
, zamiast tego użyje niejawnegoconst CAT&
konstruktora kopiującego i bezpiecznie skopiuje. Żadnego niebezpieczeństwa, żadnej pułapki. Jeśli konstruktor kopiujący jest wyłączony z jakiegokolwiek powodu, pojawi się błąd kompilatora.wydruki
COPY
, nieMOVE
.http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f
Zauważ, że błąd we wspomnianym kodzie jest problemem z wydajnością , a nie ze stabilnością , więc taki błąd nigdy nie spowoduje awarii. Po prostu użyje wolniejszej kopii. Ponadto taki błąd występuje również w przypadku obiektów niebędących stałymi, które nie mają konstruktorów przenoszenia, więc samo dodanie
const
przeciążenia nie złapie ich wszystkich. Moglibyśmy sprawdzić, czy istnieje możliwość przeniesienia konstrukcji lub przeniesienia przypisania z typu parametru, ale mogłoby to kolidować z ogólnym kodem szablonu, który powinien powrócić do konstruktora kopiującego. I do cholery, może ktoś chce móc konstruowaćconst CAT&&
, kim ja jestem, żeby powiedzieć, że nie może?źródło
const
również nie pomoże. [class.copy] §8: "W przeciwnym razie niejawnie zadeklarowany konstruktor kopiujący będzie miał postaćX::X(X&)
"Jednym z powodów, dla których dotychczas pominięto pozostałe odpowiedzi, jest to, że kod ogólny jest odporny na ruch. Na przykład powiedzmy, że chciałem napisać ogólną funkcję, która przenosi wszystkie elementy z jednego rodzaju kontenera, aby utworzyć inny rodzaj kontenera o tych samych wartościach:
Fajnie, teraz mogę stosunkowo sprawnie stworzyć
vector<string>
z adeque<string>
i każda osobastring
zostanie przeniesiona w tym procesie.Ale co, jeśli chcę się przenieść z
map
?Gdyby
std::move
nalegać na brakconst
argumentu, powyższa instancjamove_each
nie skompilowałaby się, ponieważ próbuje przenieść aconst int
(key_type
zmap
). Ale ten kod nie dba o to, czy nie może przenieść plikukey_type
. Chce przenieśćmapped_type
(std::string
) ze względu na wydajność.To w tym przykładzie i niezliczonych innych przykładach podobnych do tego w kodowaniu ogólnym
std::move
jest to żądanie przeniesienia , a nie żądanie ruchu.źródło
Mam te same obawy co OP.
std :: move nie przenosi obiektu, ani nie gwarantuje, że obiekt jest ruchomy. Więc dlaczego nazywa się to ruchem?
Myślę, że brak możliwości ruchu może być jednym z dwóch następujących scenariuszy:
1. Ruchomy typ to const.
Powodem, dla którego mamy słowo kluczowe const w języku jest to, że chcemy, aby kompilator zapobiegał jakiejkolwiek zmianie obiektu zdefiniowanego jako const. Biorąc pod uwagę przykład w książce Scotta Meyersa:
Co to dosłownie oznacza? Przenieś ciąg const do elementu członkowskiego wartości - przynajmniej tak rozumiem, zanim przeczytam wyjaśnienie.
Jeśli język nie zamierza się poruszać lub nie gwarantuje, że ruch ma zastosowanie, gdy wywoływana jest funkcja std :: move (), to jest to dosłownie mylące, gdy używa się słowa move.
Jeśli język zachęca ludzi używających std :: move do lepszej wydajności, musi jak najwcześniej zapobiegać takim pułapkom, szczególnie w przypadku tego typu oczywistej dosłownej sprzeczności.
Zgadzam się, że ludzie powinni mieć świadomość, że przeniesienie stałej jest niemożliwe, ale ten obowiązek nie powinien oznaczać, że kompilator może milczeć, gdy wydarzy się oczywista sprzeczność.
2. Obiekt nie ma konstruktora przenoszenia
Osobiście uważam, że to odrębna historia od troski OP, jak powiedział Chris Drew
źródło
Jestem zaskoczony, że nikt nie wspomniał o aspekcie kompatybilności wstecznej. Uważam, że
std::move
został celowo zaprojektowany do tego w C ++ 11. Wyobraź sobie, że pracujesz ze starszą bazą kodów, która w dużym stopniu opiera się na bibliotekach C ++ 98, więc bez rezerwowego przypisania kopii przenoszenie może zepsuć wszystko.źródło
Na szczęście możesz skorzystać z czeku clang-tidy, aby znaleźć takie problemy: https://clang.llvm.org/extra/clang-tidy/checks/performance-move-const-arg.html
źródło