Menu
Coddy logo textTech

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 leer

Markieren 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.

challenge icon

Aufgabe

Einfach

Lassen 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 eine DataPacket-Klasse, die eine Nutzlast (Payload) von Ganzzahldaten verwaltet. Ihre Klasse sollte Folgendes enthalten:
    • Private Member: einen Zeiger auf ein Integer-Array (payload), eine size für die Anzahl der Elemente und eine packetId (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 noexcept markiert 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)
  • main.cpp: Demonstrieren Sie die Move-Konstruktion, indem Sie eine Paket-ID und eine Größe von der Eingabe lesen. Dann:
    • Erstellen Sie ein DataPacket namens original mit den Eingabewerten
    • Geben Sie "Original - ID: <id>, Size: <size>, Sum: <sum>" aus
    • Erstellen Sie ein neues Paket namens transferred, indem Sie mit std::move() von original verschieben
    • 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

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 empty

Markieren 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;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung