Move-Konstruktor
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 21 von 104.
Ein Move-Konstruktor überträgt Ressourcen von einem temporären Objekt, anstatt sie zu kopieren. Während ein Copy-Konstruktor Daten dupliziert, „stiehlt“ ein Move-Konstruktor die Ressourcen und hinterlässt das Quellobjekt in einem gültigen, aber leeren Zustand. Dadurch werden teure tiefe Kopien vermieden, wenn das Quellobjekt ohnehin kurz davorsteht, zerstört zu werden.
Der Move-Konstruktor nimmt eine Rvalue-Referenz entgegen, die durch && bezeichnet wird:
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// Move-Konstruktor
Buffer(Buffer&& other) noexcept {
data = other.data; // Den Pointer übernehmen
size = other.size;
other.data = nullptr; // Quelle in einem gültigen Zustand hinterlassen
other.size = 0;
}
~Buffer() { delete[] data; }
};Der entscheidende Unterschied zum Kopieren: Wir weisen keinen neuen Speicher zu. Wir übernehmen einfach den Besitz des vorhandenen Speichers und setzen den Zeiger der Quelle auf nullptr, damit dessen Destruktor unsere Daten nicht löscht.
Move-Konstruktoren werden beim Initialisieren aus temporären Objekten oder bei der Verwendung von std::move() aufgerufen:
Buffer createBuffer() {
return Buffer(1000); // Move-Konstruktor verwendet (Rückgabewert)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // Explizites Verschieben - b1 ist jetzt leerMarkiere Move-Konstruktoren nach Möglichkeit als noexcept. Dadurch wird dem Compiler mitgeteilt, dass die Operation keine Ausnahmen auslöst, wodurch wichtige Optimierungen in Containern wie std::vector ermöglicht werden.
Aufgabe
EinfachErstellen wir ein Datenpaketsystem, das zeigt, wie Verschiebe-Konstruktoren den Besitz von Ressourcen effizient übertragen. Du erstellst eine DataPacket-Klasse, die ein dynamisch zugewiesenes Byte-Array verwaltet, und implementierst einen Verschiebe-Konstruktor, der die Daten „stiehlt“, anstatt sie zu kopieren.
Du erstellst zwei Dateien, um deinen Code zu organisieren:
DataPacket.h: Definiere eineDataPacket-Klasse, die eine Nutzlast aus Ganzzahldaten verwaltet. Deine Klasse sollte Folgendes enthalten:- Private Member: einen Zeiger auf ein Ganzzahl-Array (
payload), einesizefür die Anzahl der Elemente und einepacketId(Zeichenkette) zur Identifizierung des Pakets - Einen parametrisierten Konstruktor, der eine Paket-ID und eine Größe entgegennimmt, das Array zuweist und es mit Werten von 0 bis size-1 füllt. Gib
"Packet <id> created with size <size>"aus - Einen mit
noexceptgekennzeichneten Verschiebe-Konstruktor, der den Besitz der Nutzlast vom Quellobjekt übernimmt. Gib"Packet <id> moved"aus. Denke daran, die Quelle in einem gültigen leeren Zustand zu hinterlassen (nullptr, Größe 0) - Einen Destruktor, der den Speicher freigibt, wenn der Zeiger nicht null ist, und
"Packet <id> destroyed"ausgibt - Eine
getSize()-Methode, die die aktuelle Größe zurückgibt - Eine
getId()-Methode, die die Paket-ID zurückgibt - Eine
getSum()-Methode, die die Summe aller Elemente in der Nutzlast zurückgibt (gib 0 zurück, wenn payload null ist)
- Private Member: einen Zeiger auf ein Ganzzahl-Array (
main.cpp: Führe die Verschiebe-Konstruktion vor, indem du eine Paket-ID und eine Größe aus der Eingabe liest. Anschließend:- Erstelle ein
DataPacketnamensoriginalmit den Eingabewerten - Gib
"Original - ID: <id>, Size: <size>, Sum: <sum>"aus - Erstelle ein neues Paket namens
transferred, indem du mithilfe vonoriginalden Besitz vonstd::move()überträgst - Gib
"After move:"aus - Gib
"Original - ID: <id>, Size: <size>, Sum: <sum>"aus - Gib
"Transferred - ID: <id>, Size: <size>, Sum: <sum>"aus
- Erstelle ein
Das Eingabeformat lautet:
- Erste Zeile: Paket-ID (Zeichenkette)
- Zweite Zeile: Größe (Ganzzahl)
Nach der Verschiebung sollte das ursprüngliche Paket die Größe 0 und die Summe 0 haben (da seine Daten übertragen wurden), während das übertragene Paket alle ursprünglichen Daten enthalten sollte. Dies zeigt, wie Verschiebe-Semantik teure tiefe Kopien vermeidet, indem sie einfach den Besitz des Zeigers überträgt.
Binde deine Header-Datei in main.cpp mit #include "DataPacket.h" ein und vergiss nicht, <utility> für std::move() einzubinden.
Probier es selbst
#include <iostream>
#include <string>
#include <utility>
#include "DataPacket.h"
using namespace std;
int main() {
// Eingabe lesen
string packetId;
int size;
cin >> packetId;
cin >> size;
// TODO: Erstelle ein DataPacket namens 'original' mit den Eingabewerten
// TODO: Gib "Original - ID: <id>, Size: <size>, Sum: <sum>" aus
// TODO: Erstelle ein neues Paket namens 'transferred', indem du von 'original' verschiebst
// Hinweis: Verwende std::move()
// TODO: Gib "After move:" aus
// TODO: Gib "Original - ID: <id>, Size: <size>, Sum: <sum>" aus
// TODO: Gib "Transferred - ID: <id>, Size: <size>, Sum: <sum>" aus
return 0;
}Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler