In C ++ muss der Programmierer entscheiden, wie die verwendeten Ressourcen freigegeben werden. Es gibt keine automatischen Tools wie den Garbage Collector. Der Artikel beschreibt mögliche Lösungen für dieses Problem, untersucht mögliche Probleme im Detail sowie eine Reihe verwandter Probleme.
Inhaltsverzeichnis
Einführung
Ressourcenverwaltung ist etwas, das ein C ++ - Programmierer ständig tun muss. Zu den Ressourcen gehören Speicherblöcke, Betriebssystemkernobjekte, Multithread-Sperren, Netzwerkverbindungen, Datenbankverbindungen und nur alle im dynamischen Speicher erstellten Objekte. Der Zugriff auf die Ressource erfolgt über einen Deskriptor. Der Typ des Deskriptors ist normalerweise ein Zeiger oder einer seiner Aliase ( HANDLE
usw.), manchmal der gesamte (UNIX-Dateideskriptoren). Nachdem Sie die Ressource verwendet haben, müssen Sie sie freigeben. Andernfalls gehen einer Anwendung, die keine Ressourcen freigibt (und möglicherweise anderen Anwendungen), früher oder später die Ressourcen aus. Dieses Problem ist sehr akut. Wir können sagen, dass eine der Hauptfunktionen von .NET, Java und mehreren anderen Plattformen ein einheitliches Ressourcenverwaltungssystem ist, das auf der Speicherbereinigung basiert.
Die objektorientierten Funktionen von C ++ führen natürlich zu der folgenden Lösung: Die Klasse, die die Ressource verwaltet, enthält den Ressourcendeskriptor als Mitglied, initialisiert den Deskriptor, wenn die Ressource erfasst wird, und gibt die Ressource im Destruktor frei. Aber nach einigem Nachdenken (oder Erleben) kommt das Verständnis, dass es nicht so einfach ist. Das Hauptproblem ist die Semantik des Kopierens. Wenn die Klasse, die die Ressource verwaltet, den vom Standard-Compiler generierten Kopierkonstruktor verwendet, erhalten wir nach dem Kopieren des Objekts zwei Kopien des Handles derselben Ressource. Wenn ein Objekt eine Ressource freigibt, kann das zweite danach versuchen, die bereits freigegebene Ressource zu verwenden oder freizugeben. Dies ist in jedem Fall falsch und kann zu einem sogenannten undefinierten Verhalten führen, dh es kann alles passieren, z. B. eine abnormale Beendigung des Programms.
Glücklicherweise kann ein Programmierer in C ++ den Kopiervorgang vollständig steuern, indem er selbst einen Kopierkonstruktor und einen Kopierzuweisungsoperator definiert, wodurch wir das oben genannte Problem lösen können, und dies normalerweise auf mehrere Arten. Die Implementierung des Kopierens sollte eng mit dem Mechanismus der Freigabe der Ressource verknüpft sein, und wir werden dies gemeinsam als Strategie für den Besitz von Kopien bezeichnen. Die sogenannte „Regel der großen Drei“ ist bekannt. Wenn ein Programmierer mindestens eine der drei Operationen definiert - Kopierkonstruktor, Kopierzuweisungsoperator oder Destruktor -, muss er alle drei Operationen definieren. Copy-Ownership-Strategien legen lediglich fest, wie dies zu tun ist. Es gibt vier grundlegende Strategien für den Besitz von Kopien.
1. Grundlegende Strategien für den Besitz von Kopien
Vor der Erfassung der Ressource oder nach ihrer Freigabe muss der Deskriptor einen speziellen Wert annehmen, der angibt, dass er nicht mit der Ressource verknüpft ist. Normalerweise ist dies Null, manchmal -1, umgewandelt in einen Deskriptortyp. In jedem Fall wird ein solcher Deskriptor als Null bezeichnet. Die Klasse, die die Ressource verwaltet, muss den Nulldeskriptor erkennen und darf in diesem Fall nicht versuchen, die Ressource zu verwenden oder freizugeben.
1.1. Kopierverbotsstrategie
Dies ist die einfachste Strategie. In diesem Fall ist es einfach verboten, Klasseninstanzen zu kopieren und zuzuweisen. Der Destruktor gibt die erfasste Ressource frei. In C ++ ist es nicht schwierig, das Kopieren zu verhindern. Die Klasse muss den geschlossenen Kopierkonstruktor und den Kopierzuweisungsoperator deklarieren, aber nicht definieren.
class X { private: X(const X&); X& operator=(const X&);
Kopierversuche werden vom Compiler und Linker vereitelt.
Der C ++ 11-Standard bietet für diesen Fall eine spezielle Syntax:
class X { public: X(const X&) = delete; X& operator=(const X&) = delete;
Diese Syntax ist visueller und gibt dem Compiler beim Versuch, zu kopieren, verständlichere Meldungen.
In der vorherigen Version der Standardbibliothek (C ++ 98) verwendeten die Klassen von Eingabe- / Ausgabestreams ( std::fstream
usw.) die std::fstream
und unter Windows viele Klassen von MFC ( CFile
, CEvent
, CMutex
usw.). In der C ++ 11-Standardbibliothek verwenden einige Klassen diese Strategie, um die Multithread-Synchronisation zu unterstützen.
1.2. Exklusive Eigentümerstrategie
In diesem Fall wird beim Implementieren des Kopierens und Zuweisen der Ressourcendeskriptor vom Quellobjekt zum Zielobjekt verschoben, dh er verbleibt in einer einzelnen Kopie. Nach dem Kopieren oder Zuweisen hat das Quellobjekt einen Nulldeskriptor und kann die Ressource nicht verwenden. Der Destruktor gibt die erfasste Ressource frei. Für diese Strategie werden auch die Begriffe exklusives oder striktes Eigentum [Josuttis] verwendet, Andrei Alexandrescu verwendet den Begriff destruktives Kopieren. In C ++ 11 geschieht dies wie folgt: Regelmäßiges Kopieren und Kopieren ist auf die oben beschriebene Weise verboten, und die Bewegungssemantik wird implementiert, dh der Verschiebungskonstruktor und der Verschiebungszuweisungsoperator werden definiert. (Mehr zur Semantik der Bewegung später.)
class X { public: X(const X&) = delete; X& operator=(const X&) = delete; X(X&& src) noexcept; X& operator=(X&& src) noexcept;
Somit kann die Exklusivbesitzstrategie als Erweiterung der Kopierverbotsstrategie angesehen werden.
In der C ++ 11-Standardbibliothek verwendet diese Strategie den Smart Pointer std::unique_ptr<>
und einige andere Klassen, z. B. std::thread
, std::unique_lock<>
, sowie Klassen, die zuvor die std::unique_lock<>
( std::unique_lock<>
verwendet haben std::fstream
usw.). Unter Windows verwendeten MFC-Klassen, die zuvor die CFile
, auch die exklusive Eigentumsstrategie ( CFile
, CEvent
, CMutex
usw.).
1.3. Deep Copy-Strategie
In diesem Fall können Sie Klasseninstanzen kopieren und zuweisen. Es ist erforderlich, den Kopierkonstruktor und den Kopierzuweisungsoperator zu definieren, damit das Zielobjekt die Ressource vom Quellobjekt in sich selbst kopiert. Danach besitzt jedes Objekt seine Kopie der Ressource und kann die Ressource unabhängig verwenden, ändern und freigeben. Der Destruktor gibt die erfasste Ressource frei. Manchmal wird für Objekte, die die Deep Copy-Strategie verwenden, der Begriff Wertobjekte verwendet.
Diese Strategie gilt nicht für alle Ressourcen. Es kann auf Ressourcen angewendet werden, die einem Speicherpuffer zugeordnet sind, z. B. Zeichenfolgen. Es ist jedoch nicht klar, wie es auf Objekte des Betriebssystemkerns wie Dateien, Mutexe usw. angewendet werden soll.
Die Deep Copy-Strategie wird in allen Arten von Objektzeichenfolgen, std::vector<>
und anderen Containern der Standardbibliothek verwendet.
1.4. Miteigentumsstrategie
In diesem Fall können Sie Klasseninstanzen kopieren und zuweisen. Sie müssen den Kopierkonstruktor und den Kopierzuweisungsoperator definieren, in den der Ressourcendeskriptor (sowie andere Daten) kopiert wird, nicht jedoch die Ressource selbst. Danach hat jedes Objekt eine eigene Kopie des Deskriptors, kann die Ressource verwenden, ändern, aber nicht freigeben, solange mindestens ein weiteres Objekt eine Kopie des Deskriptors besitzt. Eine Ressource wird freigegeben, nachdem das letzte Objekt, das eine Kopie des Handles besitzt, den Gültigkeitsbereich verlassen hat. Wie dies implementiert werden kann, wird unten beschrieben.
Miteigentumsstrategien werden häufig von intelligenten Zeigern verwendet, und es ist auch selbstverständlich, sie für unveränderliche Ressourcen zu verwenden. Der Smart Pointer std::shared_ptr<>
implementiert diese Strategie in der C ++ 11-Standardbibliothek.
2. Deep Copy-Strategie - Probleme und Lösungen
Betrachten Sie eine Vorlage für die Statusaustauschfunktion von Objekten vom Typ T
in der C ++ 98-Standardbibliothek.
template<typename T> void swap(T& a, T& b) { T tmp(a); a = b; b = tmp; }
Wenn Typ T
eine Ressource besitzt und eine Deep-Copy-Strategie verwendet, haben wir drei Operationen, um eine neue Ressource zuzuweisen, drei Kopieroperationen und drei Operationen, um Ressourcen freizugeben. Während dieser Vorgang in den meisten Fällen ausgeführt werden kann, ohne neue Ressourcen zuzuweisen und überhaupt zu kopieren, reicht es aus, wenn die Objekte interne Daten austauschen, einschließlich eines Ressourcendeskriptors. Es gibt viele ähnliche Beispiele, wenn Sie temporäre Kopien einer Ressource erstellen und diese sofort freigeben müssen. Eine solche ineffektive Implementierung des täglichen Betriebs stimulierte die Suche nach Lösungen für ihre Optimierung. Betrachten wir die Hauptoptionen.
2.1. Aufzeichnen kopieren
Copy on Write (COW), auch als verzögerte Kopie bezeichnet, kann als Versuch angesehen werden, eine Deep-Copy-Strategie und eine Shared-Ownership-Strategie zu kombinieren. Beim Kopieren eines Objekts wird zunächst der Ressourcendeskriptor ohne die Ressource selbst kopiert, und für Eigentümer wird die Ressource freigegeben und schreibgeschützt. Sobald jedoch ein Eigentümer die freigegebene Ressource ändern muss, wird die Ressource kopiert, und dieser Eigentümer arbeitet mit seiner eine Kopie. Die Implementierung von COW löst das Problem des staatlichen Austauschs: Eine zusätzliche Zuweisung von Ressourcen und ein Kopieren erfolgt nicht. Die Verwendung von COW ist bei der Implementierung von Zeichenfolgen sehr beliebt, z. B. CString
(MFC, ATL). Eine Diskussion möglicher Wege zur Implementierung von COW und neu auftretenden Problemen findet sich in [Meyers1], [Sutter]. [Guntheroth] schlug eine COW-Implementierung mit std::shared_ptr<>
. Bei der Implementierung von COW in einer Multithread-Umgebung treten Probleme auf. Daher ist es verboten, COW für Zeichenfolgen in der Standard-C ++ 11-Bibliothek zu verwenden (siehe [Josuttis], [Guntheroth]).
Die Entwicklung der COW-Idee führt zu folgendem Ressourcenverwaltungsschema: Die Ressource ist unveränderlich und wird von Objekten mithilfe der gemeinsamen Eigentümerstrategie verwaltet. Falls erforderlich, um die Ressource zu ändern, wird eine neue, entsprechend geänderte Ressource erstellt und ein neues Eigentümerobjekt zurückgegeben. Dieses Schema wird für Zeichenfolgen und andere unveränderliche Objekte auf den .NET- und Java-Plattformen verwendet. In der funktionalen Programmierung wird es für komplexere Datenstrukturen verwendet.
2.2. Definieren einer Zustandsaustauschfunktion für eine Klasse
Es wurde oben gezeigt, wie ineffizient die Zustandsaustauschfunktion sein kann, die auf einfache Weise durch Kopieren und Zuweisen implementiert wird. Und es wird ziemlich häufig verwendet, zum Beispiel wird es von vielen Algorithmen der Standardbibliothek verwendet. Damit die Algorithmen nicht ein anderes std::swap()
, sondern eine andere speziell für die Klasse definierte Funktion, müssen zwei Schritte ausgeführt werden.
1. Definieren Sie in der Klasse eine Mitgliedsfunktion Swap()
(der Name ist nicht wichtig), die den Austausch von Zuständen implementiert.
class X { public: void Swap(X& other) noexcept;
Sie müssen sicherstellen, dass diese Funktion keine Ausnahmen noexcept
. In C ++ 11 müssen solche Funktionen als noexcept
deklariert werden.
2. Definieren Sie im selben Namespace wie Klasse X
(normalerweise in derselben Header-Datei) die freie Funktion (nicht Mitglied) swap()
wie folgt (Name und Signatur sind grundlegend):
inline void swap(X& a, X& b) noexcept { a.Swap(b); }
Danach wird es von den Algorithmen der Standardbibliothek verwendet, nicht von std::swap()
. Dies bietet einen Mechanismus, der als argumentabhängige Suche (ADL) bezeichnet wird. Weitere Informationen zu ADL finden Sie unter [Dewhurst1].
In der C ++ - Standardbibliothek implementieren alle Container, Smart Pointer sowie andere Klassen die Statusaustauschfunktion wie oben beschrieben.
Die Swap()
-Mitgliedsfunktion ist normalerweise einfach zu definieren: Es ist erforderlich, nacheinander eine Statusaustauschoperation auf die Datenbanken und Mitglieder anzuwenden, sofern sie dies unterstützen, und ansonsten auf std::swap()
.
Die obige Beschreibung ist etwas vereinfacht, eine detailliertere findet sich in [Meyers2]. Eine Diskussion von Fragen im Zusammenhang mit der staatlichen Austauschfunktion findet sich auch in [Sutter / Alexandrescu].
Die Zustandsaustauschfunktion kann einer der Grundoperationen der Klasse zugeordnet werden. Mit dieser Funktion können Sie andere Vorgänge ordnungsgemäß definieren. Beispielsweise wird der Kopierzuweisungsoperator durch copy und Swap()
wie folgt definiert:
X& X::operator=(const X& src) { X tmp(src); Swap(tmp); return *this; }
Diese Vorlage wird als Kopier- und Austauschsprache oder Herb Sutter-Sprache bezeichnet. Weitere Informationen finden Sie unter [Sutter], [Sutter / Alexandrescu], [Meyers2]. Seine Modifikation kann angewendet werden, um die Semantik der Verschiebung zu implementieren, siehe Abschnitte 2.4, 2.6.1.
2.3. Zwischenkopien vom Compiler entfernen
Betrachten Sie die Klasse
class X { public: X();
Und funktionieren
X Foo() {
Mit einem einfachen Ansatz wird die Rückkehr von der Foo()
Funktion durch Kopieren der Instanz von X
Compiler können den Kopiervorgang jedoch aus dem Code entfernen, das Objekt wird direkt am Aufrufpunkt erstellt. Dies wird als Rückgabewertoptimierung (RVO) bezeichnet. RVO wird seit einiger Zeit von Compiler-Entwicklern verwendet und ist derzeit im C ++ 11-Standard festgelegt. Obwohl die Entscheidung über RVO vom Compiler getroffen wird, kann der Programmierer Code basierend auf seiner Verwendung schreiben. Zu diesem Zweck ist es wünschenswert, dass die Funktion einen Rückgabepunkt hat und der Typ des zurückgegebenen Ausdrucks mit dem Typ des Rückgabewerts der Funktion übereinstimmt. In einigen Fällen ist es ratsam, einen speziellen geschlossenen Konstruktor zu definieren, der als „Rechenkonstruktor“ bezeichnet wird. Weitere Einzelheiten finden Sie unter [Dewhurst2]. RVO wird auch in [Meyers3] und [Guntheroth] diskutiert.
Compiler können in anderen Situationen Zwischenkopien löschen.
2.4. Implementierung der Verschiebungssemantik
Die Implementierung der Verschiebungssemantik besteht darin, einen Verschiebungskonstruktor zu definieren, der einen Parameter vom Typ rWertreferenz auf die Quelle und einen Verschiebungszuweisungsoperator mit demselben Parameter enthält.
In der C ++ 11-Standardbibliothek ist die Funktionsvorlage für den Statusaustausch wie folgt definiert:
template<typename T> void swap(T& a, T& b) { T tmp(std::move(a)); a = std::move(b); b = std::move(tmp); }
In Übereinstimmung mit den Regeln zum Auflösen von Überladungen von Funktionen mit Parametern vom Typ rWertreferenz (siehe Anhang A) werden diese verwendet, wenn der Typ T
einen sich bewegenden Konstruktor und einen sich bewegenden Zuweisungsoperator hat, und es werden keine temporären Ressourcen zugewiesen und kopiert. Andernfalls werden der Kopierkonstruktor und der Kopierzuweisungsoperator verwendet.
Durch die Verwendung der Semantik der Verlagerung wird vermieden, dass temporäre Kopien in einem viel breiteren Kontext als der oben beschriebenen Zustandsaustauschfunktion erstellt werden. Die Bewegungssemantik gilt für jeden r-Wert, dh einen temporären, unbenannten Wert, sowie für den Rückgabewert einer Funktion, wenn sie lokal erstellt wurde (einschließlich l-Wert) und RVO nicht angewendet wurde. In all diesen Fällen ist garantiert, dass das Quellobjekt nach dem Verschieben in keiner Weise verwendet werden kann. Die Verschiebungssemantik gilt auch für den Wert lvalue, auf den die Transformation std::move()
angewendet wird. In diesem Fall ist der Programmierer jedoch dafür verantwortlich, wie die Quellobjekte nach dem Verschieben verwendet werden (Beispiel std::swap()
).
Die Standard-C ++ 11-Bibliothek wurde unter Berücksichtigung der Bewegungssemantik neu gestaltet. Viele Klassen haben einen Verschiebungskonstruktor und einen Verschiebungszuweisungsoperator sowie andere Elementfunktionen mit Parametern vom Typ rWertreferenz hinzugefügt. Zum Beispiel hat std::vector<T>
eine überladene Version von void push_back(T&& src)
. All dies ermöglicht es in vielen Fällen, das Erstellen temporärer Kopien zu vermeiden.
Durch die Implementierung der Verschiebungssemantik werden die Definitionen der Statusaustauschfunktion für eine Klasse nicht aufgehoben. Eine speziell definierte Zustandsaustauschfunktion kann effizienter sein als die Standardfunktion std::swap()
. Darüber hinaus lassen sich der Verschiebungskonstruktor und der Verschiebungszuweisungsoperator sehr einfach unter Verwendung der Elementfunktion des Zustandsaustauschs wie folgt definieren (Variation der Kopier- und Austauschsprache):
class X { public: X() noexcept {} void Swap(X& other) noexcept {} X(X&& src) noexcept : X() { Swap(src); } X& operator=(X&& src) noexcept { X tmp(std::move(src));
Der Verschiebungskonstruktor und der Verschiebungszuweisungsoperator sind diejenigen noexcept
für die es äußerst wünschenswert ist, sicherzustellen, dass sie keine Ausnahmen noexcept
und dementsprechend als noexcept
deklariert werden. Auf diese Weise können Sie einige Operationen der Standardbibliothekscontainer optimieren, ohne die strikte Garantie für die Sicherheit von Ausnahmen zu verletzen. Weitere Informationen finden Sie unter [Meyers3] und [Guntheroth]. Die vorgeschlagene Vorlage bietet eine solche Garantie, sofern der Standardkonstruktor und die Mitgliedsfunktion des Staatenaustauschs keine Ausnahmen auslösen.
Der C ++ 11-Standard sieht vor, dass der Compiler automatisch einen Verschiebungskonstruktor und einen Verschiebungszuweisungsoperator generiert. Dazu müssen sie mit dem Konstrukt "=default"
deklariert werden.
class X { public: X(X&&) = default; X& operator=(X&&) = default;
Operationen werden implementiert, indem die Verschiebungsoperation nacheinander auf die Basen und Mitglieder der Klasse angewendet wird, sofern sie die Verschiebung unterstützen, und Operationen ansonsten kopiert werden. Es ist klar, dass diese Option bei weitem nicht immer akzeptabel ist. Rohe Deskriptoren werden nicht verschoben, können jedoch normalerweise nicht kopiert werden. Unter bestimmten Bedingungen kann der Compiler unabhängig voneinander einen ähnlichen Verschiebungskonstruktor und Verschiebungszuweisungsoperator generieren. Es ist jedoch besser, diese Gelegenheit nicht zu nutzen. Diese Bedingungen sind ziemlich verwirrend und können sich leicht ändern, wenn die Klasse verfeinert wird. Siehe [Meyers3] für Details.
Im Allgemeinen ist die Implementierung und Verwendung der Semantik der Verschiebung eine ziemlich "subtile Sache". Der Compiler kann das Kopieren dort anwenden, wo der Programmierer eine Verschiebung erwartet. Hier sind einige Regeln, um die Wahrscheinlichkeit einer solchen Situation zu beseitigen oder zumindest zu verringern.
- Verwenden Sie nach Möglichkeit das Kopierverbot.
- Deklarieren Sie den Verschiebungskonstruktor und den Verschiebungszuweisungsoperator als
noexcept
. - Implementieren Sie die Bewegungssemantik für Basisklassen und Mitglieder.
- Wenden Sie die Transformation
std::move()
auf die Parameter von Funktionen vom Typ rvalue reference an.
Regel 2 wurde oben diskutiert. 4 , rvalue- lvalue (. ). .
class B {
, . 6.2.1.
2.5. vs.
, RVO (. 2.3), , . ( ), , . , . C++11 - emplace()
, emplace_front()
, emplace_back()
, . , - — (variadic templates), . , C++11 — .
:
- , , .
- , , .
, .
std::vector<std::string> vs; vs.push_back(std::string(3, 'X'));
std::string
, . . , , . , [Meyers3].
2.6. Zusammenfassung
, , . - . . — : , . , , , . : , , «» .
: , , .NET Java. , Clone()
Duplicate()
.
- - , :
- .
- .
- - rvalue-.
.NET Java - , , .NET IClonable
. , .
3.
, . - , . , . Windows: , HANDLE
, COM-. DuplicateHandle()
, CloseHandle()
. COM- - IUnknown::AddRef()
IUnknown::Release()
. ATL ComPtr<>
, COM- . UNIX, C, _dup()
, .
C++11 std::shared_ptr<>
. , , , , , . , . std::shared_ptr<>
[Josuttis], [Meyers3].
: - , ( ). ( ) , . std::shared_ptr<>
std::weak_ptr<>
. . [Josuttis], [Meyers3].
- [Alexandrescu]. ( ) , [Schildt]. , .
( ) [Alger].
-. [Josuttis] [Alexandrescu].
- .NET Java. , , , .
4.
, C++ rvalue- . C++98 std::auto_ptr<>
, , , . , , ( ). C++11 rvalue- , , . C++11 std::auto_ptr><>
std::unique_ptr<>
. , [Josuttis], [Meyers3].
: - ( std::fstream
, etc.), ( std::thread
, std::unique_lock<>
, etc.). MFC , ( CFile
, CEvent
, CMutex
, etc.).
5. —
. , . , , , . , , , ( ) . , , , . ( ) , . , . — . 6.
, - -, « », - . - . , , , , - . «».
6. -
, - . , -. .
6.1.
- . , , :
- . , .
- .
- .
, , , . C++11 .
« » (resource acquisition is initialization, RAII). RAII ( ), ., [Dewhurst1]. «» RAII. , , , (immutable) RAII.
6.2.
, RAII, , , . - , , - . , , , . .
6.2.1.
, , , , :
- , .
- .
- .
- .
C++11 , , , . , - clear()
, , , . . , shrink_to_fit()
, , (. ).
, RAII, , , . , .
class X { public:
.
X x;
std::thread
.
2.4, - . , - - . .
class X {
:
X::X(X&& src) noexcept : X() { Swap(src); } X& X::operator=(X&& src) noexcept { X tmp(std::move(src));
- :
void X::Create() { X tmp();
, , , - . , , , . , .
- « », , . : , , ( ). : , . , : , , . , . [Sutter], [Sutter/Alexandrescu], [Meyers2].
, RAII .
6.2.2.
RAII . , , , , :
- , .
- .
- . , .
- .
- .
«» RAII, — . , , . 3. . «», .
6.2.3.
— . RAII , . , . , , ( -). - ( -). 6.2.1, .
6.3.
, - RAII, : . , , .
7.
, , , , . - -.
4 -:
- .
- .
- .
- .
. , - : , , - .
, . , , -, , .
- . . , (. 6.2.3). , (. 6.2.1). , . , , . , std::shared_ptr<>
.
Anwendungen
. Rvalue-
Rvalue- C++ , , rvalue-. rvalue- T
T&&
.
:
class Int { int m_Value; public: Int(int val) : m_Value(val) {} int Get() const { return m_Value; } void Set(int val) { m_Value = val; } };
, rvalue- .
Int&& r0;
rvalue- ++ , lvalue. Ein Beispiel:
Int i(7); Int&& r1 = i;
rvalue:
Int&& r2 = Int(42);
lvalue rvalue-:
Int&& r4 = static_cast<Int&&>(i);
rvalue- ( ) std::move()
, ( <utility>
).
Rvalue rvalue , .
int&& r5 = 2 * 2;
rvalue- .
Int&& r = 7; std::cout << r.Get() << '\n';
Rvalue- .
Int&& r = 5; Int& x = r;
Rvalue- , . , rvalue-, rvalue .
void Foo(Int&&); Int i(7); Foo(i);
, rvalue rvalue- , . rvalue-.
, , , rvalue-, (ambiguous) rvalue .
void Foo(Int&&); void Foo(const Int&);
Int i(7); Foo(i);
: rvalue- lvalue.
Int&& r = 7; Foo(r);
, rvalue-, lvalue std::move()
. . 2.4.
++11, rvalue- — -. (lvalue/rvalue) this
.
class X { public: X(); void DoIt() &;
.
, ( std::string
, std::vector<>
, etc.) . — . , rvalue- . , , - , - , . , , , rvalue, lvalue. , rvalue. . , ( lvalue), RVO.
Referenzliste
[Alexandrescu]
, . C++.: . aus dem Englischen - M.: LLC “I.D. », 2002.
[Guntheroth]
, . C++. .: . aus dem Englischen — .: «-», 2017.
[Josuttis]
, . C++: , 2- .: . aus dem Englischen - M.: LLC “I.D. », 2014.
[Dewhurst1]
, . C++. , 2- .: . aus dem Englischen — .: -, 2013.
[Dewhurst2]
, . C++. .: . aus dem Englischen — .: , 2012.
[Meyers1]
, . C++. 35 .: . aus dem Englischen — .: , 2000.
[Meyers2]
, . C++. 55 .: . aus dem Englischen — .: , 2014.
[Meyers3]
, . C++: 42 C++11 C ++14.: . aus dem Englischen - M.: LLC “I.D. », 2016.
[Sutter]
, . C++.: . aus dem Englischen — : «.. », 2015.
[Sutter/Alexandrescu]
, . , . ++.: . aus dem Englischen - M.: LLC “I.D. », 2015.
[Schildt]
, . C++.: . aus dem Englischen — .: -, 2005.
[Alger]
, . C++: .: . aus dem Englischen — .: « «», 1999.