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 aufWenn 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.
Aufgabe
EinfachLassen 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 eineMessageLog-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
capacityfür die maximale Anzahl von Nachrichten und einencountfü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
- Private Member: einen Zeiger auf ein String-Array zum Speichern von Nachrichten, eine
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
MessageLognamensoriginalmit der eingegebenen Kapazität - Fügen Sie beide Nachrichten zu
originalhinzu - Erstellen Sie eine Kopie namens
backupmithilfe des Kopierkonstruktors - Fügen Sie eine dritte Nachricht
"New message"nur zuoriginalhinzu - Geben Sie
"Original count: <count>"aus - Geben Sie
"Backup count: <count>"aus - Geben Sie alle Nachrichten aus
backupaus, eine pro Zeile
- Erstellen Sie ein
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 aufWenn 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;
}
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