Menu
Coddy logo textTech

Kopierkonstruktor

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

Ein Kopierkonstruktor erstellt ein neues Objekt als Kopie eines bestehenden Objekts. Er wird aufgerufen, wenn Sie ein Objekt aus einem anderen Objekt desselben Typs initialisieren, ein Objekt als Wert übergeben oder ein Objekt als Wert zurückgeben.

Der Kopierkonstruktor nimmt eine const-Referenz auf ein Objekt derselben Klasse entgegen:

class Player {
    std::string name;
    int health;
public:
    Player(std::string n, int h) : name(n), health(h) {}
    
    // Kopierkonstruktor
    Player(const Player& other) {
        name = other.name;
        health = other.health;
    }
};

Player original("Hero", 100);
Player copy = original;    // Kopierkonstruktor aufgerufen
Player another(original);  // Ruft ebenfalls den Kopierkonstruktor auf

Wenn Sie keinen Kopierkonstruktor definieren, erzeugt der Compiler einen, der eine flache Kopie (shallow copy) ausführt – er kopiert den Wert jedes Elements direkt. Dies funktioniert bei einfachen Typen gut, verursacht jedoch Probleme, wenn Ihre Klasse dynamischen Speicher verwaltet.

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s) {
        data = new int[size];
    }
    
    // Tiefer Kopierkonstruktor
    Buffer(const Buffer& other) : size(other.size) {
        data = new int[size];              // Neuen Speicher reservieren
        for (size_t i = 0; i < size; i++)
            data[i] = other.data[i];       // Inhalte kopieren
    }
    
    ~Buffer() { delete[] data; }
};

Ohne einen benutzerdefinierten Kopierkonstruktor würden beide Objekte auf denselben Speicher verweisen. Wenn eines zerstört wird, hätte das andere einen hängenden Zeiger. Eine Tiefe Kopie reserviert neuen Speicher und kopiert die tatsächlichen Daten, wodurch sichergestellt wird, dass jedes Objekt seine eigenen Ressourcen besitzt.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Nachrichtenverlaufssystem erstellen, das die Bedeutung des tiefen Kopierens (Deep Copy) demonstriert, wenn Ihre Klasse dynamischen Speicher verwaltet. Sie werden eine MessageLog-Klasse erstellen, die Nachrichten in einem dynamisch zugewiesenen Array speichert, und einen ordnungsgemäßen Kopierkonstruktor implementieren, um sicherzustellen, dass jede Kopie über ihren eigenen unabhängigen Speicher verfügt.

Sie werden zwei Dateien erstellen, um Ihren Code zu organisieren:

  • MessageLog.h: Definieren Sie eine MessageLog-Klasse, die eine Sammlung von Nachrichten mithilfe von dynamischem Speicher verwaltet. Ihre Klasse sollte Folgendes enthalten:
    • Private Member: einen Zeiger auf ein String-Array zum Speichern von Nachrichten, eine capacity für die maximale Anzahl von Nachrichten und einen count für die aktuelle Anzahl der Nachrichten
    • Einen parametrisierten Konstruktor, der eine Kapazität entgegennimmt, das Array auf dem Heap alloziert und count auf 0 initialisiert
    • Einen Kopierkonstruktor, der eine tiefe Kopie (deep copy) durchführt — neuen Speicher alloziert und alle Nachrichten vom Quellobjekt kopiert
    • Einen Destruktor, der den allozierten Speicher freigibt und "MessageLog destroyed" ausgibt
    • Eine addMessage(string msg)-Methode, die eine Nachricht hinzufügt, sofern Platz vorhanden ist
    • Eine getCount()-Methode, die die aktuelle Anzahl der Nachrichten zurückgibt
    • Eine getMessage(int index)-Methode, die die Nachricht am angegebenen Index zurückgibt
  • main.cpp: Demonstrieren Sie, dass Ihr Kopierkonstruktor eine unabhängige Kopie erstellt. Lesen Sie einen Kapazitätswert und zwei Nachrichten von der Eingabe ein, und führen Sie dann Folgendes aus:
    • Erstellen Sie ein MessageLog namens original mit der eingegebenen Kapazität
    • Fügen Sie beide Nachrichten zu original hinzu
    • Erstellen Sie eine Kopie namens backup mithilfe des Kopierkonstruktors
    • Fügen Sie eine dritte Nachricht "New message" nur zu original hinzu
    • Geben Sie "Original count: <count>" aus
    • Geben Sie "Backup count: <count>" aus
    • Geben Sie alle Nachrichten aus backup aus, eine pro Zeile

Das Eingabeformat ist wie folgt:

  • Erste Zeile: Kapazität (Ganzzahl)
  • Zweite Zeile: erste Nachricht (String)
  • Dritte Zeile: zweite Nachricht (String)

Wenn Ihre tiefe Kopie korrekt implementiert ist, sollte das Backup 2 Nachrichten enthalten, während das Original 3 hat — was beweist, dass sie über unabhängigen Speicher verfügen. Ohne einen ordnungsgemäßen Kopierkonstruktor würden beide dasselbe Array teilen, und Änderungen an einem würden das andere beeinflussen!

Binden Sie Ihre Header-Datei in main.cpp mit #include "MessageLog.h" ein.

Spickzettel

Ein Kopierkonstruktor erstellt ein neues Objekt als Kopie eines bestehenden Objekts. Er nimmt eine const-Referenz auf ein Objekt derselben Klasse entgegen:

class Player {
    std::string name;
    int health;
public:
    Player(std::string n, int h) : name(n), health(h) {}
    
    // Kopierkonstruktor
    Player(const Player& other) {
        name = other.name;
        health = other.health;
    }
};

Player original("Hero", 100);
Player copy = original;    // Kopierkonstruktor aufgerufen
Player another(original);  // Ruft ebenfalls den Kopierkonstruktor auf

Wenn Sie keinen Kopierkonstruktor definieren, generiert der Compiler einen, der eine flache Kopie (shallow copy) durchführt – dabei wird der Wert jedes Elements direkt kopiert. Dies führt zu Problemen, wenn Ihre Klasse dynamischen Speicher verwaltet.

Ein tiefer Kopierkonstruktor (deep copy constructor) reserviert neuen Speicher und kopiert die tatsächlichen Daten, wodurch sichergestellt wird, dass jedes Objekt seine eigenen Ressourcen besitzt:

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s) {
        data = new int[size];
    }
    
    // Tiefer Kopierkonstruktor
    Buffer(const Buffer& other) : size(other.size) {
        data = new int[size];              // Neuen Speicher reservieren
        for (size_t i = 0; i < size; i++)
            data[i] = other.data[i];       // Inhalte kopieren
    }
    
    ~Buffer() { delete[] data; }
};

Ohne einen benutzerdefinierten Kopierkonstruktor würden beide Objekte auf denselben Speicher verweisen, was zu hängenden Zeigern (dangling pointers) führt, wenn eines der Objekte zerstört wird.

Probier es selbst

#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;

int main() {
    // Eingabe lesen
    int capacity;
    cin >> capacity;
    cin.ignore();
    
    string msg1, msg2;
    getline(cin, msg1);
    getline(cin, msg2);

    // TODO: Erstelle ein MessageLog namens 'original' mit der eingegebenen Kapazität

    // TODO: Füge beide Nachrichten zu original hinzu

    // TODO: Erstelle eine Kopie namens 'backup' unter Verwendung des copy constructor

    // TODO: Füge "New message" nur zu original hinzu

    // TODO: Gib "Original count: <count>" aus

    // TODO: Gib "Backup count: <count>" aus

    // TODO: Gib alle Nachrichten aus backup aus, eine pro Zeile

    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