Menu
Coddy logo textTech

Move-Semantik & Rvalues

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 83 von 104.

In C++ ist jeder Ausdruck entweder ein lvalue (besitzt eine dauerhafte Identität, kann adressiert werden) oder ein rvalue (temporär, steht kurz vor der Zerstörung). Das Verständnis dieser Unterscheidung ermöglicht Move-Semantik – eine leistungsstarke Optimierung, die unnötiges Kopieren vermeidet.

Eine rvalue-Referenz, deklariert mit &&, bindet spezifisch an temporäre Objekte. Dies ermöglicht es Ihnen, Ressourcen von Objekten zu „stehlen“, die ohnehin bald verschwinden:

#include <iostream>
#include <utility>

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : data(new int[s]), size(s) {
        std::cout << "Constructed\n";
    }
    
    // Move-Konstruktor - stiehlt Ressourcen
    Buffer(Buffer&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;  // Hinterlässt die Quelle in einem gültigen Zustand
        other.size = 0;
        std::cout << "Moved\n";
    }
    
    ~Buffer() { delete[] data; }
};

int main() {
    Buffer b1(1000);
    Buffer b2(std::move(b1));  // Löst den Move-Konstruktor aus
}

Die Funktion std::move verschiebt eigentlich gar nichts – sie wandelt lediglich einen lvalue in eine rvalue-Referenz um und signalisiert damit, dass Sie bereit sind, die Ressourcen des Objekts aufzugeben. Das eigentliche Verschieben findet im Move-Konstruktor oder im Move-Zuweisungsoperator statt.

Move-Semantiken verbessern die Performance drastisch, wenn mit ressourcenintensiven Objekten wie Containern oder Strings gearbeitet wird. Anstatt Megabytes an Daten tief zu kopieren, übertragen Sie einfach den Besitz des Pointers – eine Operation in konstanter Zeit, unabhängig von der Größe.

challenge icon

Aufgabe

Einfach

Lassen Sie uns eine Ressourcen-verwaltende DataBuffer-Klasse erstellen, die Move-Semantik in Aktion demonstriert. Sie werden sehen, wie das Verschieben von Ressourcen anstelle des Kopierens die Effizienz beim Übertragen des Eigentums an dynamisch zugewiesenem Speicher drastisch verbessern kann.

Sie werden Ihren Code über drei Dateien organisieren:

  • DataBuffer.h: Definieren Sie Ihre DataBuffer-Klasse, die ein dynamisch zugewiesenes Array von Integern verwaltet.

    Ihre Klasse sollte private Member für den Daten-Pointer (int*), die Größe (size_t) und einen Namen (std::string) haben, um zu verfolgen, welcher Buffer welcher ist während der Operationen.

    Deklarieren Sie Folgendes:

    • Einen Konstruktor, der einen std::string Namen und eine size_t Größe entgegennimmt, das Array allokiert und ausgibt: [name] constructed with size [size]
    • Einen Move-Konstruktor, der eine Rvalue-Referenz entgegennimmt, die Ressourcen stiehlt und ausgibt: [name] moved from [source_name] (wobei der Ziel-Buffer den Namen der Quelle übernimmt)
    • Einen Destruktor, der [name] destroyed ausgibt (oder empty destroyed, wenn der Buffer verschoben wurde)
    • Eine getSize()-Methode, die die aktuelle Größe zurückgibt
    • Eine getName()-Methode, die den Namen des Buffers zurückgibt

    Denken Sie daran, Ihren Move-Konstruktor als noexcept zu markieren und das Quellobjekt in einem gültigen leeren Zustand zu hinterlassen (nullptr, Größe 0, Name "empty").

  • DataBuffer.cpp: Implementieren Sie alle in Ihrem Header deklarierten Methoden. Wenn der Destruktor ausgeführt wird, löschen Sie die Daten nur, wenn der Pointer nicht null ist. Binden Sie <iostream> für die Ausgabe ein.
  • main.cpp: Lesen Sie zwei Eingaben ein:
    1. Einen Namen für Ihren Buffer (String)
    2. Eine Größe für Ihren Buffer (Integer)

    Erstellen Sie einen DataBuffer mit dem angegebenen Namen und der Größe. Erstellen Sie dann einen zweiten Buffer, indem Sie den ersten mit std::move() verschieben. Geben Sie nach dem Verschieben den Zustand beider Buffer aus:

    • Original: [name] size=[size]
    • New: [name] size=[size]

    Binden Sie <utility> für std::move ein.

Zum Beispiel mit den Eingaben Alpha und 100:

Alpha constructed with size 100
Alpha moved from Alpha
Original: empty size=0
New: Alpha size=100
Alpha destroyed
empty destroyed

Mit den Eingaben Buffer und 50:

Buffer constructed with size 50
Buffer moved from Buffer
Original: empty size=0
New: Buffer size=50
Buffer destroyed
empty destroyed

Beachten Sie, wie der Move-Konstruktor das Eigentum am allokierten Speicher überträgt, ohne Daten zu kopieren. Der ursprüngliche Buffer wird in einem leeren, aber gültigen Zustand hinterlassen, und wenn beide Buffer am Ende des Programms zerstört werden, löscht nur derjenige, der noch Eigentümer des Speichers ist, diesen tatsächlich.

Probier es selbst

#include <iostream>
#include <string>
#include <utility>
#include "DataBuffer.h"

int main() {
    std::string name;
    int size;
    
    std::cin >> name;
    std::cin >> size;
    
    // TODO: Erstellen Sie einen DataBuffer mit dem angegebenen Namen und der Größe
    
    // TODO: Erstellen Sie einen zweiten Puffer, indem Sie den ersten mit std::move() verschieben
    
    // TODO: Geben Sie den Zustand beider Puffer aus:
    // Original: [name] Größe=[size]
    // Neu: [name] Größe=[size]
    
    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