Byłem zaskoczony, że to nie pojawiło się w moich wynikach wyszukiwania, pomyślałem, że ktoś by zapytał o to wcześniej, biorąc pod uwagę użyteczność semantyki ruchu w C ++ 11:
Kiedy muszę (lub czy jest to dobry pomysł) uczynić klasę nieruchomą w C ++ 11?
( To znaczy przyczyny inne niż problemy ze zgodnością z istniejącym kodem).
c++
c++11
move-semantics
c++-faq
user541686
źródło
źródło
+1
ode mnie) z bardzo dokładną odpowiedzią od Herba (lub jego bliźniaka, jak się wydaje ), więc zrobiłem to wpis w FAQ. Jeśli ktoś sprzeciwi się, po prostu pinguje mnie w salonie , więc można to tam omówić.T x = std::move(anotherT);
bycie legalnym” nie są równoważne. To ostatnie jest żądaniem ruchu, które może spaść z powrotem na kopiującego w przypadku, gdy T nie ma żadnego z nich. Więc co dokładnie oznacza „ruchomy”?Odpowiedzi:
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ślistd::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 nichstd::mutex
musiałby przechowywać natywny typ muteksu na stercie, aby pozostał na w tej samej lokalizacji, gdy są przenoszone międzystd::mutex
obiektami lubstd::mutex
nie mogą się poruszać. Przechowywanie go na stercie nie jest możliwe, ponieważstd::mutex
maconstexpr
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ę dostd::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 anint
, możesz skopiować jego wartość, a następnie ustawić ją na zero, ale nadal jest toint
z wartością, to tylko bajty pamięci. Aleint
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ć.źródło
Krótka odpowiedź: jeśli typ można skopiować, powinien być również przenośny. Jednak sytuacja odwrotna nie jest prawdą: niektóre typy, takie jak,
std::unique_ptr
są ruchome, ale kopiowanie ich nie ma sensu; są to oczywiście typy tylko do przenoszenia.Nieco dłuższa odpowiedź następuje ...
Istnieją dwa główne rodzaje typów (między innymi te bardziej specjalnego przeznaczenia, takie jak cechy):
Typy wartościowe, takie jak
int
lubvector<widget>
. Reprezentują one wartości i naturalnie powinny być kopiowalne. W C ++ 11 generalnie powinieneś myśleć o przenoszeniu jako o optymalizacji kopiowania, więc wszystkie typy do kopiowania powinny być naturalnie przenośne ... przenoszenie jest po prostu skutecznym sposobem wykonywania kopii w często powszechnym przypadku, którego nie robisz ' nie potrzebuję już oryginalnego obiektu i i tak go zniszczę.Typy podobne do odwołań, które istnieją w hierarchiach dziedziczenia, takich jak klasy podstawowe i klasy z wirtualnymi lub chronionymi funkcjami składowymi. Są one zwykle utrzymywane przez wskaźnik lub odwołanie, często
base*
lubbase&
, więc nie zapewniają konstrukcji kopiowania, aby uniknąć cięcia; jeśli chcesz uzyskać inny obiekt, taki jak istniejący, zwykle wywołujesz funkcję wirtualną, taką jakclone
. Nie wymagają one konstrukcji ani przypisania przesunięcia z dwóch powodów: Nie można ich kopiować i mają już jeszcze bardziej wydajną naturalną operację „przenoszenia” - po prostu kopiujesz / przesuwasz wskaźnik do obiektu, a sam obiekt nie w ogóle trzeba przenieść do nowej lokalizacji pamięci.Większość typów należy do jednej z tych dwóch kategorii, ale są też inne typy, które są również przydatne, tylko rzadziej. W szczególności tutaj typy, które wyrażają unikalną własność zasobu, takie jak
std::unique_ptr
, są naturalnie typami tylko do przenoszenia, ponieważ nie są wartościowe (nie ma sensu ich kopiować), ale używasz ich bezpośrednio (nie zawsze za pomocą wskaźnika lub odniesienia) i dlatego chcą przenosić obiekty tego typu z jednego miejsca do drugiego.źródło
std::mutex
jest nieruchome, ponieważ muteksy POSIX są używane przez adres.Właściwie, kiedy rozglądam się po okolicy, zauważyłem, że wiele typów w C ++ 11 nie jest ruchomych:
mutex
typy (recursive_mutex
,timed_mutex
,recursive_timed_mutex
,condition_variable
type_info
error_category
locale::facet
random_device
seed_seq
ios_base
basic_istream<charT,traits>::sentry
basic_ostream<charT,traits>::sentry
atomic
typyonce_flag
Najwyraźniej jest dyskusja na temat Clang: https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c++/pCO1Qqb3Xa4
źródło
iterators / iterator adaptors
powinno być edytowane, ponieważ C ++ 11 ma move_iterator?std::reference_wrapper
. Ok, inne rzeczywiście wydają się być nieruchome.ios_base
,type_info
,facet
), 3. Różne dziwne rzeczy (sentry
). Prawdopodobnie jedyne nieruchome klasy, które napisze przeciętny programista, należą do drugiej kategorii.Kolejny powód, który znalazłem - wydajność. Powiedzmy, że masz klasę „a”, która ma wartość. Chcesz wyświetlić interfejs, który pozwala użytkownikowi zmienić wartość przez ograniczony czas (dla zakresu).
Sposobem na osiągnięcie tego jest zwrócenie obiektu `` scope guard '' z `` a '', który ustawia wartość z powrotem w jego destruktorze, na przykład:
Gdybym sprawił, że change_value_guard był ruchomy, musiałbym dodać „if” do jego destruktora, który sprawdzałby, czy osłona została przeniesiona - to dodatkowe „jeśli” i wpływ na wydajność.
Tak, jasne, prawdopodobnie można to zoptymalizować za pomocą dowolnego rozsądnego optymalizatora, ale nadal fajnie, że język (wymaga to jednak C ++ 17, aby móc zwrócić nieruchomy typ, wymaga gwarantowanej elizji kopiowania) nie wymaga nas zapłacić, jeśli i tak nie zamierzamy przesunąć strażnika, poza zwróceniem go z funkcji tworzącej (zasada nie-płacić za to, czego nie używasz).
źródło