Menu
Coddy logo textTech

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 leer

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

challenge icon

Aufgabe

Einfach

Erstellen 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 eine DataPacket-Klasse, die eine Nutzlast aus Ganzzahldaten verwaltet. Deine Klasse sollte Folgendes enthalten:
    • Private Member: einen Zeiger auf ein Ganzzahl-Array (payload), eine size für die Anzahl der Elemente und eine packetId (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 noexcept gekennzeichneten 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)
  • 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 DataPacket namens original mit den Eingabewerten
    • Gib "Original - ID: <id>, Size: <size>, Sum: <sum>" aus
    • Erstelle ein neues Paket namens transferred, indem du mithilfe von original den Besitz von std::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

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;
}
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

Übe selbstständig: Online-C++-Compiler