Verschiebekonstruktor
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 Kopierkonstruktor Daten dupliziert, „stiehlt“ ein Move-Konstruktor die Ressourcen und hinterlässt das Quellobjekt in einem gültigen, aber leeren Zustand. Dies vermeidet teure tiefe Kopien, wenn das Quellobjekt ohnehin zerstört werden soll.
Der Move-Konstruktor erwartet eine rvalue-Referenz, gekennzeichnet durch &&:
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; // Pointer stehlen
size = other.size;
other.data = nullptr; // Quelle in gültigem Zustand hinterlassen
other.size = 0;
}
~Buffer() { delete[] data; }
};Der entscheidende Unterschied zum Kopieren: Wir reservieren keinen neuen Speicher. 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 aufgerufen, wenn von temporären Objekten initialisiert wird oder wenn std::move() verwendet wird:
Buffer createBuffer() {
return Buffer(1000); // Move-Konstruktor verwendet (Rückgabewert)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // Explizites Verschieben - b1 ist jetzt leerMarkieren Sie Move-Konstruktoren nach Möglichkeit als noexcept. Dies teilt dem Compiler mit, dass die Operation keine Exceptions auslösen wird, was wichtige Optimierungen in Containern wie std::vector ermöglicht.
Aufgabe
EinfachLassen Sie uns ein Datenpaketsystem erstellen, das demonstriert, wie Move-Konstruktoren den Besitz von Ressourcen effizient übertragen. Sie werden eine DataPacket-Klasse erstellen, die ein dynamisch zugewiesenes Array von Bytes verwaltet, und einen Move-Konstruktor implementieren, der die Daten "stiehlt", anstatt sie zu kopieren.
Sie werden zwei Dateien erstellen, um Ihren Code zu organisieren:
DataPacket.h: Definieren Sie eineDataPacket-Klasse, die eine Nutzlast (Payload) von Ganzzahldaten verwaltet. Ihre Klasse sollte Folgendes enthalten:- Private Member: einen Zeiger auf ein Integer-Array (
payload), einesizefür die Anzahl der Elemente und einepacketId(String) zur Identifizierung des Pakets - Einen parametrisierten Konstruktor, der eine Paket-ID und eine Größe entgegennimmt, das Array allokiert und es mit Werten von 0 bis size-1 füllt. Geben Sie
"Packet <id> created with size <size>"aus - Einen Move-Konstruktor, der als
noexceptmarkiert ist und den Besitz der Nutzlast vom Quellobjekt überträgt. Geben Sie"Packet <id> moved"aus. Denken Sie daran, die Quelle in einem gültigen leeren Zustand zu belassen (nullptr, size 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 (geben Sie 0 zurück, wenn die Nutzlast null ist)
- Private Member: einen Zeiger auf ein Integer-Array (
main.cpp: Demonstrieren Sie die Move-Konstruktion, indem Sie eine Paket-ID und eine Größe von der Eingabe lesen. Dann:- Erstellen Sie ein
DataPacketnamensoriginalmit den Eingabewerten - Geben Sie
"Original - ID: <id>, Size: <size>, Sum: <sum>"aus - Erstellen Sie ein neues Paket namens
transferred, indem Sie mitstd::move()vonoriginalverschieben - Geben Sie
"After move:"aus - Geben Sie
"Original - ID: <id>, Size: <size>, Sum: <sum>"aus - Geben Sie
"Transferred - ID: <id>, Size: <size>, Sum: <sum>"aus
- Erstellen Sie ein
Das Eingabeformat ist:
- Erste Zeile: Paket-ID (String)
- Zweite Zeile: Größe (Ganzzahl)
Nach dem Verschieben 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 Move-Semantiken teure tiefe Kopien vermeiden, indem sie einfach den Besitz des Zeigers übertragen.
Binden Sie Ihre Header-Datei in main.cpp mit #include "DataPacket.h" ein und vergessen Sie nicht, <utility> für std::move() einzubinden.
Spickzettel
Ein Move-Konstruktor überträgt Ressourcen von einem temporären Objekt, anstatt sie zu kopieren, wodurch teure tiefe Kopien vermieden werden. Er nimmt eine Rvalue-Referenz (&&) entgegen und „stiehlt“ Ressourcen, wobei das Quellobjekt in einem gültigen, aber leeren Zustand zurückgelassen wird.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// Move constructor
Buffer(Buffer&& other) noexcept {
data = other.data; // Steal the pointer
size = other.size;
other.data = nullptr; // Leave source in valid state
other.size = 0;
}
~Buffer() { delete[] data; }
};Move-Konstruktoren werden aufgerufen, wenn eine Initialisierung durch temporäre Objekte erfolgt oder wenn std::move() verwendet wird:
Buffer createBuffer() {
return Buffer(1000); // Move constructor used (return value)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // Explicit move - b1 is now emptyMarkieren Sie Move-Konstruktoren nach Möglichkeit als noexcept, um Optimierungen in Standard-Containern zu ermöglichen.
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
// Hint: 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 & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container