Ich war überrascht, dass dies nicht in meinen Suchergebnissen angezeigt wurde. Ich dachte, jemand hätte dies zuvor gefragt, da die Verschiebungssemantik in C ++ 11 nützlich ist:
Wann muss ich (oder ist es eine gute Idee für mich) eine Klasse in C ++ 11 unbeweglich machen?
( Andere Gründe als Kompatibilitätsprobleme mit vorhandenem Code.)
c++
c++11
move-semantics
c++-faq
user541686
quelle
quelle
+1
von mir) mit einer sehr gründlichen Antwort von Herb (oder seinem Zwilling, wie es scheint ), also habe ich daraus einen FAQ-Eintrag gemacht. Wenn jemand etwas dagegen hat, mich in der Lounge anzupingen , kann dies dort besprochen werden.T x = std::move(anotherT);
legal sein" nicht gleichwertig sind. Letzteres ist eine Verschiebungsanforderung, die auf den Kopiercode zurückgreifen kann, falls T keinen Verschiebungsfaktor hat. Was bedeutet "beweglich" genau?Antworten:
Die Antwort von Herb (bevor sie bearbeitet wurde) gab tatsächlich ein gutes Beispiel für einen Typ, der nicht beweglich sein sollte :
std::mutex
.Der native Mutex-Typ des Betriebssystems (z. B.
pthread_mutex_t
auf POSIX-Plattformen) ist möglicherweise nicht "standortinvariant", was bedeutet, dass die Adresse des Objekts Teil seines Werts ist. Beispielsweise kann das Betriebssystem eine Liste von Zeigern auf alle initialisierten Mutex-Objekte führen. Wennstd::mutex
ein Mutex-Typ des nativen Betriebssystems als Datenelement enthalten wäre und die Adresse des nativen Typs fest bleiben muss (da das Betriebssystem eine Liste von Zeigern auf seine Mutexe verwaltet),std::mutex
müsste entweder der native Mutex-Typ auf dem Heap gespeichert werden, damit er erhalten bleibt der gleiche Ort, wenn zwischenstd::mutex
Objekten verschoben oder diestd::mutex
nicht verschoben werden dürfen. Das Speichern auf dem Heap ist nicht möglich, da astd::mutex
einenconstexpr
Konstruktor hat und für eine konstante Initialisierung (dh statische Initialisierung) geeignet sein muss, damit eine globalestd::mutex
wird garantiert erstellt, bevor die Programmausführung beginnt, sodass der Konstruktor sie nicht verwenden kannnew
. Die einzige Möglichkeit iststd::mutex
also, unbeweglich zu sein.Die gleiche Überlegung gilt für andere Typen, die etwas enthalten, für das eine feste Adresse erforderlich ist. Wenn die Adresse der Ressource unverändert bleiben muss, verschieben Sie sie nicht!
Es gibt ein weiteres Argument für
std::mutex
das Nichtbewegen, nämlich, dass es sehr schwierig wäre, dies sicher zu tun, da Sie wissen müssen, dass niemand versucht, den Mutex zu sperren, sobald er verschoben wird. Da Mutexe einer der Bausteine sind, mit denen Sie Datenrennen verhindern können, wäre es bedauerlich, wenn sie nicht selbst gegen Rennen sicher wären! Mit einem Unbeweglichenstd::mutex
wissen Sie, dass das einzige, was jeder daran tun kann, wenn es erstellt wurde und bevor es zerstört wurde, darin besteht, es zu sperren und zu entsperren. Diese Vorgänge sind ausdrücklich threadsicher und führen keine Datenrennen ein. Das gleiche Argument gilt fürstd::atomic<T>
Objekte: Wenn sie nicht atomar verschoben werden könnten, wäre es nicht möglich, sie sicher zu verschieben. Möglicherweise versucht ein anderer Thread, sie aufzurufencompare_exchange_strong
auf dem Objekt genau in dem Moment, in dem es bewegt wird. Ein weiterer Fall, in dem Typen nicht beweglich sein sollten, besteht darin, dass sie einfache Bausteine für sicheren gleichzeitigen Code sind und die Atomizität aller Operationen auf ihnen sicherstellen müssen. Wenn der Objektwert zu irgendeinem Zeitpunkt in ein neues Objekt verschoben werden kann, müssen Sie eine atomare Variable verwenden, um jede atomare Variable zu schützen, damit Sie wissen, ob die Verwendung sicher ist oder verschoben wurde ... und eine atomare Variable zum Schutz diese atomare Variable und so weiter ...Ich denke, ich würde verallgemeinern, um zu sagen, dass es keinen Sinn macht, ein Objekt zu verschieben, wenn es nur ein reines Erinnerungsstück ist, kein Typ, der als Halter für einen Wert oder eine Abstraktion eines Wertes fungiert. Grundlegende Typen wie "
int
Nicht verschieben": Das Verschieben ist nur eine Kopie. Sie können die Eingeweide nicht aus einem herausreißenint
, Sie können seinen Wert kopieren und dann auf Null setzen, aber es ist immer noch einint
mit einem Wert, es sind nur Bytes Speicher. Aber einint
ist noch beweglichin den Sprachbegriffen, weil eine Kopie eine gültige Verschiebungsoperation ist. Wenn Sie jedoch bei nicht kopierbaren Typen den Speicher nicht verschieben möchten oder können und auch seinen Wert nicht kopieren können, ist er nicht verschiebbar. Ein Mutex oder eine atomare Variable ist ein bestimmter Speicherort (der mit speziellen Eigenschaften behandelt wird), daher ist das Verschieben nicht sinnvoll und kann auch nicht kopiert werden, sodass er nicht verschoben werden kann.quelle
Kurze Antwort: Wenn ein Typ kopierbar ist, sollte er auch beweglich sein. Jedoch ist das Gegenteil nicht wahr: Einige Typen wie
std::unique_ptr
beweglich sind , aber es macht keinen Sinn , sie zu kopieren machen; Dies sind natürlich nur bewegliche Typen.Es folgt eine etwas längere Antwort ...
Es gibt zwei Haupttypen (unter anderem spezielle Typen wie Merkmale):
Wertähnliche Typen wie
int
odervector<widget>
. Diese stellen Werte dar und sollten natürlich kopierbar sein. In C ++ 11 sollten Sie das Verschieben im Allgemeinen als Optimierung des Kopierens betrachten. Daher sollten alle kopierbaren Typen natürlich verschiebbar sein. Das Verschieben ist nur eine effiziente Methode zum Erstellen einer Kopie in dem häufig vorkommenden Fall, dass Sie dies nicht tun. Ich brauche das Originalobjekt nicht mehr und werde es trotzdem zerstören.Referenzähnliche Typen, die in Vererbungshierarchien vorhanden sind, z. B. Basisklassen und Klassen mit virtuellen oder geschützten Elementfunktionen. Diese werden normalerweise durch einen Zeiger oder eine Referenz gehalten, häufig ein
base*
oderbase&
, und bieten daher keine Kopierkonstruktion, um ein Schneiden zu vermeiden. Wenn Sie ein anderes Objekt wie ein vorhandenes erhalten möchten, rufen Sie normalerweise eine virtuelle Funktion wie aufclone
. Diese müssen aus zwei Gründen nicht verschoben oder zugewiesen werden: Sie können nicht kopiert werden und verfügen bereits über eine noch effizientere natürliche "Verschiebungs" -Operation - Sie kopieren / verschieben einfach den Zeiger auf das Objekt und das Objekt selbst nicht müssen überhaupt an einen neuen Speicherort umziehen.Die meisten Typen fallen in eine dieser beiden Kategorien, aber es gibt auch andere Arten von Typen, die ebenfalls nützlich sind, nur seltener. Insbesondere hier sind Typen, die das eindeutige Eigentum an einer Ressource ausdrücken, wie z. B.
std::unique_ptr
natürlich Nur-Verschieben-Typen, da sie nicht wertähnlich sind (es ist nicht sinnvoll, sie zu kopieren), aber Sie verwenden sie direkt (nicht immer) per Zeiger oder Referenz) und möchten daher Objekte dieses Typs von einem Ort zum anderen verschieben.quelle
std::mutex
unbeweglich, da POSIX-Mutexe nach Adresse verwendet werden.Eigentlich habe ich beim Durchsuchen festgestellt, dass einige Typen in C ++ 11 nicht beweglich sind:
mutex
Arten (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
Artenonce_flag
Anscheinend gibt es eine Diskussion über Clang: https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c++/pCO1Qqb3Xa4
quelle
iterators / iterator adaptors
sollte weg bearbeitet werden, da C ++ 11 move_iterator hat?std::reference_wrapper
. Ok, die anderen scheinen tatsächlich nicht beweglich zu sein.ios_base
,type_info
,facet
), 3. verschiedene seltsame Dinge (sentry
). Wahrscheinlich sind die einzigen unbeweglichen Klassen, die ein durchschnittlicher Programmierer schreibt, in der zweiten Kategorie.Ein weiterer Grund, den ich gefunden habe - Leistung. Angenommen, Sie haben eine Klasse 'a', die einen Wert enthält. Sie möchten eine Schnittstelle ausgeben, über die ein Benutzer den Wert für eine begrenzte Zeit (für einen Bereich) ändern kann.
Eine Möglichkeit, dies zu erreichen, besteht darin, ein 'Scope Guard'-Objekt von' a 'zurückzugeben, das den Wert in seinem Destruktor zurücksetzt, wie folgt:
Wenn ich change_value_guard beweglich machen würde, müsste ich seinem Destruktor ein 'if' hinzufügen, das prüft, ob der Guard entfernt wurde - das ist ein zusätzliches Wenn und eine Auswirkung auf die Leistung.
Ja, sicher, es kann wahrscheinlich von jedem vernünftigen Optimierer optimiert werden, aber es ist trotzdem schön, dass die Sprache (dies erfordert jedoch C ++ 17, um einen nicht beweglichen Typ zurückgeben zu können, eine garantierte Kopierelision erfordert) uns nicht benötigt das zu bezahlen, wenn wir den Wächter sowieso nicht bewegen wollen, außer ihn von der Erstellungsfunktion zurückzugeben (das Prinzip, nicht für das zu bezahlen, was Sie nicht verwenden).
quelle