Odpowiedź Herba (zanim została zredagowana) faktycznie podała dobry przykład typu, którego nie można przenosić:std::mutex
.
Natywny typ muteksu systemu operacyjnego (np. pthread_mutex_t
Na platformach POSIX) może nie być „niezmienny względem lokalizacji”, co oznacza, że adres obiektu jest częścią jego wartości. Na przykład system operacyjny może przechowywać listę wskaźników do wszystkich zainicjowanych obiektów mutex. Jeśli std::mutex
zawiera natywny typ muteksu systemu operacyjnego jako element członkowski danych, a adres typu natywnego musi pozostać niezmieniony (ponieważ system operacyjny utrzymuje listę wskaźników do swoich muteksów), wówczas każdy z nich std::mutex
musiałby przechowywać natywny typ muteksu na stercie, aby pozostał na w tej samej lokalizacji, gdy są przenoszone między std::mutex
obiektami lub std::mutex
nie mogą się poruszać. Przechowywanie go na stercie nie jest możliwe, ponieważ std::mutex
ma constexpr
konstruktora i musi kwalifikować się do stałej inicjalizacji (tj. Inicjalizacji statycznej), aby globalnastd::mutex
jest gwarantowane, że zostanie utworzony przed rozpoczęciem wykonywania programu, więc jego konstruktor nie może go użyć new
.std::mutex
To samo rozumowanie dotyczy innych typów, które zawierają coś, co wymaga stałego adresu. Jeśli adres zasobu musi pozostać niezmieniony, nie przenoś go!
Jest jeszcze jeden argument przemawiający za tym, aby się nie ruszać, std::mutex
a mianowicie, że byłoby bardzo trudno zrobić to bezpiecznie, ponieważ musiałbyś wiedzieć, że nikt nie próbuje zablokować muteksu w momencie, gdy jest przenoszony. Ponieważ muteksy są jednym z elementów budulcowych, których możesz użyć do zapobiegania wyścigom danych, byłoby niefortunne, gdyby same nie były bezpieczne przed rasami! Z nieruchomością std::mutex
wiesz, że jedyną rzeczą, jaką każdy może z nią zrobić po jej zbudowaniu i zanim zostanie zniszczona, jest zablokowanie jej i odblokowanie, a operacje te są wyraźnie gwarantowane, że są bezpieczne dla wątków i nie wprowadzają wyścigów danych. Ten sam argument odnosi się do std::atomic<T>
obiektów: jeśli nie można ich przesunąć atomowo, nie byłoby możliwe ich bezpieczne przeniesienie, inny wątek może próbować wywołaćcompare_exchange_strong
na obiekcie w momencie, gdy jest przenoszony. Więc innym przypadkiem, w którym typy nie powinny być ruchome, jest sytuacja, w której są one niskopoziomowymi blokami konstrukcyjnymi bezpiecznego kodu współbieżnego i muszą zapewniać atomowość wszystkich operacji na nich. Jeśli wartość obiektu może zostać przeniesiona do nowego obiektu w dowolnym momencie, musisz użyć zmiennej atomowej, aby chronić każdą zmienną atomową, aby wiedzieć, czy można jej bezpiecznie używać, czy została przeniesiona ... i zmienną atomową do ochrony ta zmienna atomowa i tak dalej ...
Myślę, że uogólniłbym stwierdzenie, że kiedy obiekt jest po prostu czystą pamięcią, a nie typem, który działa jako posiadacz wartości lub abstrakcja wartości, nie ma sensu go przenosić. Podstawowe typy, takie jak int
nie można się ruszać: przenoszenie ich to tylko kopia. Nie możesz wyrwać wnętrzności z an int
, możesz skopiować jego wartość, a następnie ustawić ją na zero, ale nadal jest to int
z wartością, to tylko bajty pamięci. Ale int
jest nadal ruchomyw terminach językowych, ponieważ kopia jest prawidłową operacją przenoszenia. Jednak w przypadku typów nie do skopiowania, jeśli nie chcesz lub nie możesz przenieść fragmentu pamięci, a także nie możesz skopiować jego wartości, oznacza to, że nie można go przenieść. Muteks lub zmienna atomowa to określone miejsce w pamięci (traktowane specjalnymi właściwościami), więc nie ma sensu przenosić, a także nie można ich kopiować, więc nie można ich przenosić.