Kopierkonstruktor
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 20 von 104.
Ein Kopierkonstruktor erstellt ein neues Objekt als Kopie eines vorhandenen Objekts. Er wird aufgerufen, wenn du ein Objekt aus einem anderen Objekt desselben Typs initialisierst, ein Objekt per Wert übergibst oder ein Objekt per Wert zurückgibst.
Der Kopierkonstruktor nimmt eine konstante 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 du keinen Kopierkonstruktor definierst, erzeugt der Compiler einen, der eine flache Kopie durchführt – er kopiert den Wert jedes Members direkt. Das funktioniert bei einfachen Typen problemlos, verursacht aber Probleme, wenn deine Klasse dynamischen Speicher verwaltet.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
// Tiefkopie-Konstruktor
Buffer(const Buffer& other) : size(other.size) {
data = new int[size]; // Neuen Speicher allokieren
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 zeigen. 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
EinfachErstelle ein Nachrichtenverlaufssystem, das die Bedeutung des tiefen Kopierens demonstriert, wenn deine Klasse dynamischen Speicher verwaltet. Du erstellst eine MessageLog-Klasse, die Nachrichten in einem dynamisch reservierten Array speichert, und implementierst einen geeigneten Kopierkonstruktor, damit jede Kopie ihren eigenen unabhängigen Speicher besitzt.
Du erstellst zwei Dateien, um deinen Code zu organisieren:
MessageLog.h: Definiere eineMessageLog-Klasse, die eine Sammlung von Nachrichten mithilfe dynamischen Speichers verwaltet. Deine 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 einecountfür die aktuelle Anzahl von Nachrichten - Einen parametrisierten Konstruktor, der eine capacity entgegennimmt, das Array auf dem Heap reserviert und count auf 0 initialisiert
- Einen Kopierkonstruktor, der eine tiefe Kopie erstellt: Er reserviert neuen Speicher und kopiert alle Nachrichten aus dem Quellobjekt
- Einen Destruktor, der den reservierten Speicher freigibt und
"MessageLog destroyed"ausgibt - Eine
addMessage(string msg)-Methode, die eine Nachricht hinzufügt, wenn noch 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: Zeige, dass dein Kopierkonstruktor eine unabhängige Kopie erstellt. Lies einen capacity-Wert und zwei Nachrichten aus der Eingabe ein und:- Erstelle ein
MessageLognamensoriginalmit der eingegebenen capacity - Füge beide Nachrichten zu
originalhinzu - Erstelle mithilfe des Kopierkonstruktors eine Kopie namens
backup - Füge nur zu
"New message"eine dritte Nachrichtoriginalhinzu - Gib
"Original count: <count>"aus - Gib
"Backup count: <count>"aus - Gib alle Nachrichten aus
backupjeweils in einer eigenen Zeile aus
- Erstelle ein
Das Eingabeformat lautet:
- Erste Zeile: capacity (Ganzzahl)
- Zweite Zeile: erste Nachricht (String)
- Dritte Zeile: zweite Nachricht (String)
Wenn deine tiefe Kopie korrekt implementiert ist, sollte das Backup 2 Nachrichten enthalten, während das Original 3 Nachrichten enthält: Dies beweist, dass sie unabhängigen Speicher besitzen. Ohne einen geeigneten Kopierkonstruktor würden beide dasselbe Array verwenden, und Änderungen an einem Objekt würden sich auf das andere auswirken!
Binde deine Header-Datei in main.cpp mit #include "MessageLog.h" ein.
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 Eingabekapazität
// TODO: Füge beide Nachrichten zu original hinzu
// TODO: Erstelle eine Kopie namens 'backup' mit dem Copy-Konstruktor
// TODO: Füge "New message" nur zu original hinzu
// TODO: Print "Original count: <count>"
// TODO: Gib "Backup count: <count>" aus
// TODO: Gib alle Nachrichten von 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 & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler