Was ist Move-Semantik?

Meine erste Antwort war eine extrem vereinfachte Einführung in die Move-Semantik, und viele Details wurden absichtlich weggelassen, um sie einfach zu halten.Allerdings gibt es noch viel mehr zur Move-Semantik, und ich dachte, es wäre Zeit für eine zweite Antwort, um die Lücken zu füllen.Die erste Antwort ist schon ziemlich alt, und es fühlte sich nicht richtig an, sie einfach durch einen völlig anderen Text zu ersetzen. Ich denke, sie eignet sich immer noch gut für eine erste Einführung. Aber wenn Sie tiefer einsteigen wollen, lesen Sie weiter 🙂

Stephan T. Lavavej hat sich die Zeit genommen, wertvolles Feedback zu geben. Vielen Dank, Stephan!

Einführung

Die Bewegungssemantik erlaubt es einem Objekt, unter bestimmten Bedingungen das Eigentum an den externen Ressourcen eines anderen Objekts zu übernehmen. Das ist in zweierlei Hinsicht wichtig:

  1. Das macht teure Kopien zu billigen Verschiebungen. Siehe meine erste Antwort für ein Beispiel. Beachten Sie, dass, wenn ein Objekt nicht mindestens eine externe Ressource verwaltet (entweder direkt oder indirekt über seine Mitgliedsobjekte), die Verschiebesemantik keine Vorteile gegenüber der Kopiersemantik bietet. In diesem Fall bedeutet das Kopieren eines Objekts und das Verschieben eines Objekts genau das Gleiche:

    class cannot_benefit_from_move_semantics{ int a; // moving an int means copying an int float b; // moving a float means copying a float double c; // moving a double means copying a double char d; // moving a char array means copying a char array // ...};
  2. Implementieren von sicheren „move-only“-Typen; das heißt, Typen, für die das Kopieren keinen Sinn macht, das Verschieben aber schon. Beispiele sind Sperren, Datei-Handles und intelligente Zeiger mit eindeutiger Besitz-Semantik. Hinweis: Diese Antwort behandelt std::auto_ptr, ein veraltetes C++98-Standardbibliotheks-Template, das in C++11 durch std::unique_ptr ersetzt wurde. Fortgeschrittene C++-Programmierer sind wahrscheinlich zumindest einigermaßen mit std::auto_ptr vertraut, und wegen der „Verschiebesemantik“, die es anzeigt, scheint es ein guter Ausgangspunkt für die Diskussion der Verschiebesemantik in C++11 zu sein. YMMV.

Was ist ein Move?

Die C++98-Standardbibliothek bietet einen intelligenten Zeiger mit einzigartiger Eigentumssemantik namens std::auto_ptr<T>. Falls Sie mit auto_ptr nicht vertraut sind, sein Zweck ist es, zu garantieren, dass ein dynamisch zugewiesenes Objekt immer freigegeben wird, sogar angesichts von Ausnahmen:

{ std::auto_ptr<Shape> a(new Triangle); // ... // arbitrary code, could throw exceptions // ...} // <--- when a goes out of scope, the triangle is deleted automatically

Das Ungewöhnliche an auto_ptr ist sein „Kopier“-Verhalten:

auto_ptr<Shape> a(new Triangle); +---------------+ | triangle data | +---------------+ ^ | | | +-----|---+ | +-|-+ |a | p | | | | | +---+ | +---------+auto_ptr<Shape> b(a); +---------------+ | triangle data | +---------------+ ^ | +----------------------+ | +---------+ +-----|---+ | +---+ | | +-|-+ |a | p | | | b | p | | | | | +---+ | | +---+ | +---------+ +---------+

Beachten Sie, wie die Initialisierung von b mit a das Dreieck nicht kopiert, sondern stattdessen das Eigentum an dem Dreieck von a auf b überträgt. Wir sagen auch „a wird in b verschoben“ oder „das Dreieck wird von a nach b verschoben“. Das mag verwirrend klingen, weil das Dreieck selbst immer an der gleichen Stelle im Speicher bleibt.

Ein Objekt zu verschieben bedeutet, das Eigentum an einer Ressource, die es verwaltet, auf ein anderes Objekt zu übertragen.

Der Kopierkonstruktor von auto_ptr sieht wahrscheinlich etwa so aus (etwas vereinfacht):

auto_ptr(auto_ptr& source) // note the missing const{ p = source.p; source.p = 0; // now the source no longer owns the object}

Gefährliche und harmlose Verschiebungen

Das Gefährliche an auto_ptr ist, dass das, was syntaktisch wie eine Kopie aussieht, in Wirklichkeit eine Verschiebung ist. Der Versuch, eine Mitgliedsfunktion auf einem verschobenen auto_ptr aufzurufen, führt zu undefiniertem Verhalten, also muss man sehr vorsichtig sein, einen auto_ptr nicht zu benutzen, nachdem er verschoben wurde:

auto_ptr<Shape> a(new Triangle); // create triangleauto_ptr<Shape> b(a); // move a into bdouble area = a->area(); // undefined behavior

Aber auto_ptr ist nicht immer gefährlich. Fabrikfunktionen sind ein sehr guter Anwendungsfall für auto_ptr:

auto_ptr<Shape> make_triangle(){ return auto_ptr<Shape>(new Triangle);}auto_ptr<Shape> c(make_triangle()); // move temporary into cdouble area = make_triangle()->area(); // perfectly safe

Beachten Sie, dass beide Beispiele demselben syntaktischen Muster folgen:

auto_ptr<Shape> variable(expression);double area = expression->area();

Und dennoch ruft eines von ihnen ein undefiniertes Verhalten auf, während das andere dies nicht tut. Was ist also der Unterschied zwischen den Ausdrücken a und make_triangle()? Sind sie nicht beide vom gleichen Typ? Das sind sie in der Tat, aber sie haben unterschiedliche Wertkategorien.

Wertkategorien

Natürlich muss es einen tiefgreifenden Unterschied geben zwischen dem Ausdruck a, der eine auto_ptr-Variable bezeichnet, und dem Ausdruck make_triangle(), der den Aufruf einer Funktion bezeichnet, die eine auto_ptr als Wert zurückgibt und somit bei jedem Aufruf ein neues temporäres auto_ptr-Objekt erzeugt. a ist ein Beispiel für einen l-Wert, während make_triangle() ein Beispiel für einen r-Wert ist.

Der Wechsel von l-Werten wie a ist gefährlich, weil wir später versuchen könnten, eine Mitgliedsfunktion über a aufzurufen, was ein undefiniertes Verhalten hervorrufen würde. Andererseits ist das Verschieben von rWerten wie make_triangle() vollkommen sicher, denn nachdem der Kopierkonstruktor seine Arbeit getan hat, können wir den temporären Wert nicht mehr verwenden. Es gibt keinen Ausdruck, der dieses Provisorium bezeichnet; wenn wir einfach make_triangle() erneut schreiben, erhalten wir ein anderes Provisorium. Tatsächlich ist das verschobene Provisorium bereits in der nächsten Zeile verschwunden:

auto_ptr<Shape> c(make_triangle()); ^ the moved-from temporary dies right here

Beachten Sie, dass die Buchstaben l und r einen historischen Ursprung in der linken und rechten Seite einer Zuweisung haben. Dies ist in C++ nicht mehr der Fall, denn es gibt lWerte, die nicht auf der linken Seite einer Zuweisung erscheinen können (wie Arrays oder benutzerdefinierte Typen ohne Zuweisungsoperator), und es gibt rWerte, die dies können (alle rWerte von Klassentypen mit einem Zuweisungsoperator).

Ein rWert von Klassentyp ist ein Ausdruck, dessen Auswertung ein temporäres Objekt erzeugt. Unter normalen Umständen bezeichnet kein anderer Ausdruck innerhalb desselben Geltungsbereichs dasselbe temporäre Objekt.

RWert-Referenzen

Wir verstehen jetzt, dass der Wechsel von lWerten potentiell gefährlich ist, aber der Wechsel von rWerten ist harmlos. Wenn C++ eine Sprachunterstützung hätte, um lwertige Argumente von rwertigen Argumenten zu unterscheiden, könnten wir entweder das Verschieben von lwerten vollständig verbieten oder zumindest das Verschieben von lwerten am Aufrufort explizit machen, so dass wir nicht mehr aus Versehen verschieben.

C++11s Antwort auf dieses Problem sind rwertige Referenzen. Eine rvalue-Referenz ist eine neue Art von Referenz, die sich nur an rvalues bindet, und die Syntax ist X&&. Die gute alte Referenz X& ist jetzt als lvalue-Referenz bekannt. (Beachten Sie, dass X&& keine Referenz auf eine Referenz ist; so etwas gibt es in C++ nicht.)

Wenn wir const in den Mix werfen, haben wir bereits vier verschiedene Arten von Referenzen. An welche Arten von Ausdrücken vom Typ X können sie sich binden?

 lvalue const lvalue rvalue const rvalue--------------------------------------------------------- X& yesconst X& yes yes yes yesX&& yesconst X&& yes yes

In der Praxis kann man const X&& vergessen. Die Beschränkung auf das Lesen von rWerten ist nicht sehr nützlich.

Eine rWert-Referenz X&& ist eine neue Art von Referenz, die nur an rWerte bindet.

Implizite Konvertierungen

Wert-Referenzen haben mehrere Versionen durchlaufen. Seit Version 2.1 bindet eine rvalue-Referenz X&& auch an alle Wertkategorien eines anderen Typs Y, sofern es eine implizite Konvertierung von Y nach X gibt. In diesem Fall wird ein Provisorium des Typs X erstellt, und die rWert-Referenz ist an dieses Provisorium gebunden:

void some_function(std::string&& r);some_function("hello world");

Im obigen Beispiel ist "hello world" ein lWert des Typs const char. Da es eine implizite Konvertierung von const char über const char* nach std::string gibt, wird ein temporärer Wert des Typs std::string erstellt, und r wird an diesen temporären Wert gebunden. Dies ist einer der Fälle, in denen die Unterscheidung zwischen rValues (Ausdrücken) und Temporären (Objekten) etwas unscharf ist.

Move-Konstruktoren

Ein nützliches Beispiel für eine Funktion mit einem X&&-Parameter ist der Move-Konstruktor X::X(X&& source). Sein Zweck ist es, das Eigentum an der verwalteten Ressource von der Quelle auf das aktuelle Objekt zu übertragen.

In C++11 wurde std::auto_ptr<T> durch std::unique_ptr<T> ersetzt, das die Vorteile von rvalue-Referenzen nutzt. Ich werde eine vereinfachte Version von unique_ptr entwickeln und diskutieren. Zuerst kapseln wir einen rohen Zeiger und überladen die Operatoren -> und *, so dass sich unsere Klasse wie ein Zeiger anfühlt:

template<typename T>class unique_ptr{ T* ptr;public: T* operator->() const { return ptr; } T& operator*() const { return *ptr; }

Der Konstruktor übernimmt das Objekt, und der Destruktor löscht es:

 explicit unique_ptr(T* p = nullptr) { ptr = p; } ~unique_ptr() { delete ptr; }

Jetzt kommt der interessante Teil, der Move-Konstruktor:

 unique_ptr(unique_ptr&& source) // note the rvalue reference { ptr = source.ptr; source.ptr = nullptr; }

Dieser move-Konstruktor macht genau das, was der auto_ptr copy-Konstruktor gemacht hat, aber er kann nur mit rWerten versorgt werden:

unique_ptr<Shape> a(new Triangle);unique_ptr<Shape> b(a); // errorunique_ptr<Shape> c(make_triangle()); // okay

Die zweite Zeile lässt sich nicht kompilieren, weil a ein lWert ist, aber der Parameter unique_ptr&& source kann nur an rWerte gebunden werden. Das ist genau das, was wir wollten; gefährliche Bewegungen sollten niemals implizit sein. Die dritte Zeile lässt sich problemlos kompilieren, da make_triangle() ein r-Wert ist. Der move-Konstruktor überträgt den Besitz vom temporären Wert auf c. Auch dies ist genau das, was wir wollten.

Der move-Konstruktor überträgt das Eigentum an einer verwalteten Ressource auf das aktuelle Objekt.

Zuweisungsoperatoren verschieben

Das letzte fehlende Teil ist der Zuweisungsoperator verschieben. Seine Aufgabe ist es, die alte Ressource freizugeben und die neue Ressource aus seinem Argument zu übernehmen:

 unique_ptr& operator=(unique_ptr&& source) // note the rvalue reference { if (this != &source) // beware of self-assignment { delete ptr; // release the old resource ptr = source.ptr; // acquire the new resource source.ptr = nullptr; } return *this; }};

Beachten Sie, wie diese Implementierung des Zuweisungsoperators move die Logik sowohl des Destruktors als auch des Konstruktors move dupliziert. Sind Sie mit dem Copy-and-Swap-Idiom vertraut? Es kann auch auf die move-Semantik als move-and-swap-Idiom angewendet werden:

 unique_ptr& operator=(unique_ptr source) // note the missing reference { std::swap(ptr, source.ptr); return *this; }};

Nun, da source eine Variable des Typs unique_ptr ist, wird sie vom move-Konstruktor initialisiert; das heißt, das Argument wird in den Parameter verschoben. Das Argument muss immer noch ein r-Wert sein, da der move-Konstruktor selbst einen r-Wert-Referenzparameter hat. Wenn der Kontrollfluss die schließende Klammer von operator= erreicht, geht source aus dem Geltungsbereich heraus und gibt die alte Ressource automatisch frei.

Der Zuweisungsoperator move überträgt das Eigentum an einer verwalteten Ressource auf das aktuelle Objekt und gibt die alte Ressource frei. Das move-and-swap Idiom vereinfacht die Implementierung.

Moving from lvalues

Manchmal wollen wir von lvalues aus verschieben. Das heißt, manchmal wollen wir, dass der Compiler einen lWert so behandelt, als wäre er ein rWert, so dass er den move-Konstruktor aufrufen kann, obwohl er potenziell unsicher sein könnte.Für diesen Zweck bietet C++11 eine Standardbibliotheksfunktionsschablone namens std::move im Header <utility>.Dieser Name ist etwas unglücklich, denn std::move castet einfach einen lWert in einen rWert; es bewegt selbst nichts. Er ermöglicht lediglich das Verschieben. Vielleicht hätte es std::cast_to_rvalue oder std::enable_move heißen sollen, aber wir sind jetzt bei dem Namen hängengeblieben.

So verschiebt man explizit von einem l-Wert:

unique_ptr<Shape> a(new Triangle);unique_ptr<Shape> b(a); // still an errorunique_ptr<Shape> c(std::move(a)); // okay

Nach der dritten Zeile besitzt a kein Dreieck mehr. Das ist in Ordnung, denn durch das explizite Schreiben von std::move(a) haben wir unsere Absichten deutlich gemacht: „Lieber Konstruktor, mach mit a, was immer du willst, um c zu initialisieren; a interessiert mich nicht mehr. Du kannst mit a machen, was du willst.“

std::move(some_lvalue) wandelt einen l-Wert in einen r-Wert um und ermöglicht so einen anschließenden Umzug.

XWerte

Beachte, dass std::move(a) zwar ein r-Wert ist, seine Auswertung aber kein temporäres Objekt erzeugt. Dieses Rätsel zwang den Ausschuss, eine dritte Wertkategorie einzuführen. Etwas, das an eine rvalue-Referenz gebunden werden kann, obwohl es kein rvalue im traditionellen Sinne ist, wird als xvalue (eXpiring value) bezeichnet. Die traditionellen rvalues wurden in prvalues (Pure rvalues) umbenannt.

Bei prvalues und xvalues handelt es sich um rvalues. XWerte und lWerte sind beide glWerte (Generalized lWerte). Die Beziehungen sind mit einem Diagramm leichter zu erfassen:

 expressions / \ / \ / \ glvalues rvalues / \ / \ / \ / \ / \ / \lvalues xvalues prvalues

Beachten Sie, dass nur xvalues wirklich neu sind; der Rest ist nur auf die Umbenennung und Gruppierung zurückzuführen.

C++98 rvalues sind in C++11 als prvalues bekannt. Ersetzen Sie gedanklich alle Vorkommen von „rvalue“ in den vorhergehenden Absätzen durch „prvalue“.

Aus Funktionen herausbewegen

Bis jetzt haben wir die Bewegung in lokale Variablen und in Funktionsparameter gesehen. Aber auch in der umgekehrten Richtung ist eine Bewegung möglich. Wenn eine Funktion mit einem Wert zurückkehrt, wird ein Objekt am Aufrufort (wahrscheinlich eine lokale Variable oder ein temporäres Objekt, aber es kann jede Art von Objekt sein) mit dem Ausdruck nach der return-Anweisung als Argument für den move-Konstruktor initialisiert:

unique_ptr<Shape> make_triangle(){ return unique_ptr<Shape>(new Triangle);} \-----------------------------/ | | temporary is moved into c | vunique_ptr<Shape> c(make_triangle());

Vielleicht überraschenderweise können auch automatische Objekte (lokale Variablen, die nicht als static deklariert sind) implizit aus Funktionen verschoben werden:

unique_ptr<Shape> make_square(){ unique_ptr<Shape> result(new Square); return result; // note the missing std::move}

Warum akzeptiert der move-Konstruktor den l-Wert result als Argument? Der Gültigkeitsbereich von result wird bald enden, und er wird beim Abwickeln des Stapels zerstört werden. Niemand könnte sich hinterher beschweren, dass sich result irgendwie verändert hat; wenn der Kontrollfluss wieder beim Aufrufer ist, existiert result nicht mehr! Aus diesem Grund gibt es in C++11 eine spezielle Regel, die es erlaubt, automatische Objekte aus Funktionen zurückzugeben, ohne std::move schreiben zu müssen. Tatsächlich sollten Sie niemals std::move verwenden, um automatische Objekte aus Funktionen zu verschieben, da dies die „named return value optimization“ (NRVO) verhindert.

Verwenden Sie niemals std::move, um automatische Objekte aus Funktionen zu verschieben.

Beachten Sie, dass in beiden Factory-Funktionen der Rückgabetyp ein Wert und keine rvalue-Referenz ist. Rvalue-Referenzen sind immer noch Referenzen, und wie immer sollten Sie niemals eine Referenz auf ein automatisches Objekt zurückgeben; der Aufrufer würde mit einer baumelnden Referenz enden, wenn Sie den Compiler austricksen, damit er Ihren Code akzeptiert, wie hier:

unique_ptr<Shape>&& flawed_attempt() // DO NOT DO THIS!{ unique_ptr<Shape> very_bad_idea(new Square); return std::move(very_bad_idea); // WRONG!}

Geben Sie niemals automatische Objekte per rvalue-Referenz zurück. Das Verschieben wird ausschließlich durch den move-Konstruktor durchgeführt, nicht durch std::move, und auch nicht durch das bloße Binden eines rWertes an eine rWert-Referenz.

Moving into members

Früher oder später werden Sie Code wie diesen schreiben:

class Foo{ unique_ptr<Shape> member;public: Foo(unique_ptr<Shape>&& parameter) : member(parameter) // error {}};

Grundsätzlich wird sich der Compiler beschweren, dass parameter ein lWert ist. Wenn Sie sich den Typ ansehen, sehen Sie eine rWert-Referenz, aber eine rWert-Referenz bedeutet einfach „eine Referenz, die an einen rWert gebunden ist“; sie bedeutet nicht, dass die Referenz selbst ein rWert ist! In der Tat ist parameter nur eine gewöhnliche Variable mit einem Namen. Sie können parameter beliebig oft innerhalb des Konstruktorkörpers verwenden, und es bezeichnet immer dasselbe Objekt. Eine implizite Verschiebung von ihr wäre gefährlich, daher verbietet die Sprache dies.

Eine benannte rvalue-Referenz ist ein lvalue, genau wie jede andere Variable.

Die Lösung ist, die Verschiebung manuell zu aktivieren:

class Foo{ unique_ptr<Shape> member;public: Foo(unique_ptr<Shape>&& parameter) : member(std::move(parameter)) // note the std::move {}};

Man könnte argumentieren, dass parameter nach der Initialisierung von member nicht mehr verwendet wird. Warum gibt es keine spezielle Regel, um std::move stillschweigend einzufügen, genau wie bei Rückgabewerten? Wahrscheinlich, weil dies eine zu große Belastung für die Compiler-Implementierer wäre. Was wäre zum Beispiel, wenn der Konstruktorkörper in einer anderen Übersetzungseinheit läge? Im Gegensatz dazu muss die Rückgabewertregel lediglich die Symboltabellen überprüfen, um festzustellen, ob der Bezeichner nach dem Schlüsselwort return ein automatisches Objekt bezeichnet oder nicht.

Sie können parameter auch als Wert übergeben. Für „move-only“-Typen wie unique_ptr scheint es noch kein etabliertes Idiom zu geben. Ich persönlich bevorzuge die Wertübergabe, da sie weniger Unordnung in der Schnittstelle verursacht.

Spezielle Mitgliedsfunktionen

C++98 deklariert implizit drei spezielle Mitgliedsfunktionen bei Bedarf, d.h. wenn sie irgendwo benötigt werden: den Kopierkonstruktor, den Kopierzuweisungsoperator und den Destruktor.

X::X(const X&); // copy constructorX& X::operator=(const X&); // copy assignment operatorX::~X(); // destructor

R-Wert-Referenzen haben mehrere Versionen durchlaufen. Seit Version 3.0 deklariert C++11 zwei zusätzliche spezielle Memberfunktionen auf Anfrage: den move-Konstruktor und den move-Zuweisungsoperator. Beachten Sie, dass weder VC10 noch VC11 mit der Version 3.0 konform sind, so dass Sie diese Funktionen selbst implementieren müssen.

X::X(X&&); // move constructorX& X::operator=(X&&); // move assignment operator

Diese beiden neuen speziellen Memberfunktionen werden nur dann implizit deklariert, wenn keine der speziellen Memberfunktionen manuell deklariert wird. Auch wenn Sie Ihren eigenen Move-Konstruktor oder Move-Zuweisungsoperator deklarieren, werden weder der Copy-Konstruktor noch der Copy-Zuweisungsoperator implizit deklariert.

Was bedeuten diese Regeln in der Praxis?

Wenn Sie eine Klasse ohne unverwaltete Ressourcen schreiben, müssen Sie keine der fünf speziellen Memberfunktionen selbst deklarieren, und Sie erhalten die korrekte Copy-Semantik und Move-Semantik umsonst. Andernfalls müssen Sie die speziellen Mitgliedsfunktionen selbst implementieren. Wenn Ihre Klasse nicht von der Move-Semantik profitiert, brauchen Sie die speziellen Move-Operationen natürlich nicht zu implementieren.

Beachten Sie, dass der Kopier-Zuweisungsoperator und der Move-Zuweisungsoperator zu einem einzigen, vereinheitlichten Zuweisungsoperator verschmolzen werden können, der sein Argument als Wert annimmt:

X& X::operator=(X source) // unified assignment operator{ swap(source); // see my first answer for an explanation return *this;}

Auf diese Weise sinkt die Anzahl der zu implementierenden speziellen Mitgliedsfunktionen von fünf auf vier. Es gibt hier einen Kompromiss zwischen Ausnahmesicherheit und Effizienz, aber ich bin kein Experte in dieser Frage.

Weiterleitende Referenzen (früher bekannt als Universalreferenzen)

Betrachten Sie die folgende Funktionsvorlage:

template<typename T>void foo(T&&);

Man könnte erwarten, dass T&& nur an rWerte bindet, weil es auf den ersten Blick wie eine rWert-Referenz aussieht. Es stellt sich jedoch heraus, dass T&& auch an lWerte bindet:

foo(make_triangle()); // T is unique_ptr<Shape>, T&& is unique_ptr<Shape>&&unique_ptr<Shape> a(new Triangle);foo(a); // T is unique_ptr<Shape>&, T&& is unique_ptr<Shape>&

Wenn das Argument ein rWert des Typs X ist, wird T als X abgeleitet, also bedeutet T&& X&&. Wenn das Argument jedoch ein l-Wert vom Typ X ist, wird aufgrund einer speziellen Regel abgeleitet, dass T X& ist, so dass T&& etwas wie X& && bedeuten würde. Da C++ aber noch keinen Begriff von Referenzen auf Referenzen hat, wird der Typ X& && in X& kollabiert. Das mag sich zunächst verwirrend und nutzlos anhören, aber das Kollabieren von Referenzen ist essentiell für eine perfekte Weiterleitung (auf die hier nicht eingegangen wird).

T&& ist keine rvalue-Referenz, sondern eine Weiterleitungsreferenz. Es bindet auch an lWerte, in diesem Fall sind T und T&& beides lWert-Referenzen.

Wenn man eine Funktionsschablone auf rWerte beschränken will, kann man SFINAE mit Type Traits kombinieren:

#include <type_traits>template<typename T>typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::typefoo(T&&);

Implementierung von move

Nun, da man das Zusammenfallen von Referenzen verstanden hat, hier ist, wie std::move implementiert wird:

template<typename T>typename std::remove_reference<T>::type&&move(T&& t){ return static_cast<typename std::remove_reference<T>::type&&>(t);}

Wie Sie sehen können, akzeptiert move dank der Weiterleitungsreferenz T&& jede Art von Parameter und gibt eine rWert-Referenz zurück. Der Meta-Funktionsaufruf std::remove_reference<T>::type ist notwendig, weil der Rückgabetyp für lWerte vom Typ X sonst X& && wäre, was zu X& führen würde. Da t immer ein l-Wert ist (denken Sie daran, dass eine benannte r-Wert-Referenz ein l-Wert ist), wir aber t an eine r-Wert-Referenz binden wollen, müssen wir t explizit in den richtigen Rückgabetyp umwandeln Der Aufruf einer Funktion, die eine r-Wert-Referenz zurückgibt, ist selbst ein x-Wert. Jetzt wissen Sie, woher xvalues kommen 😉

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.