Zuweisungsoperatorüberladung
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 44 von 104.
Der Kopierzuweisungsoperator (=) wird aufgerufen, wenn Sie ein bestehendes Objekt einem anderen zuweisen. Im Gegensatz zum Kopierkonstruktor (der ein neues Objekt erstellt), arbeitet der Zuweisungsoperator mit einem Objekt, das bereits existiert und möglicherweise über Ressourcen verfügt, die zuerst bereinigt werden müssen.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
~Buffer() { delete[] data; }
Buffer& operator=(const Buffer& other) {
if (this != &other) { // 1. Prüfung auf Selbstzuweisung
delete[] data; // 2. Bestehende Ressource bereinigen
size = other.size; // 3. Daten kopieren
data = new int[size];
for (size_t i = 0; i < size; ++i)
data[i] = other.data[i];
}
return *this; // 4. *this zurückgeben
}
};Die Selbstzuweisungsprüfung (this != &other) ist entscheidend. Ohne sie würde b = b; die Daten löschen, bevor versucht wird, sie zu kopieren, was zu undefiniertem Verhalten führt.
Der Operator gibt *this per Referenz zurück, um Verkettungen wie a = b = c; zu ermöglichen. Dies entspricht dem Verhalten eingebauter Typen.
Für Klassen, die Ressourcen verwalten, werden Sie oft sowohl Kopier- als auch Verschiebe-Zuweisungsoperatoren implementieren:
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data; // Aktuelle Ressource bereinigen
data = other.data; // Ressource übernehmen
size = other.size;
other.data = nullptr; // Quelle in gültigem Zustand hinterlassen
other.size = 0;
}
return *this;
}Der Move-Zuweisungsoperator überträgt den Besitz, anstatt zu kopieren, wodurch Vorgänge wie vec[0] = createBuffer(); deutlich effizienter werden.
Aufgabe
EinfachLass uns eine DynamicString-Klasse erstellen, die ein dynamisch allokiertes Zeichenarray verwaltet und die Zuweisung zwischen bereits vorhandenen Objekten ordnungsgemäß behandelt. Dies ist ein praxisnahes Szenario, in dem du Zeichenkettendaten sicher von einem Objekt auf ein anderes übertragen musst, ohne Speicherlecks oder hängende Zeiger zu verursachen.
Du erstellst zwei Dateien, um deinen Code zu organisieren:
DynamicString.h: Definiere eineDynamicString-Klasse, die eine Zeichenkette im C-Stil mithilfe von dynamischem Speicher speichert. Deine Klasse sollte Folgendes verwalten:- Einen privaten
char*-Zeiger für die Zeichkettendaten - Eine private
size_t-Variable für die Länge - Einen Konstruktor, der einen
const char*übernimmt und eine tiefe Kopie erstellt - Einen Destruktor, der den allokierten Speicher ordnungsgemäß freigibt
- Einen Kopierzuweisungsoperator, der eine tiefe Kopie mit Schutz vor Selbstzuweisung durchführt
- Einen Verschiebezuweisungsoperator, der den Besitz effizient überträgt
- Eine
c_str()-Methode, die die interne Zeichenkette zurückgibt (const) - Eine
length()-Methode, die die Länge der Zeichenkette zurückgibt (const)
Dein Kopierzuweisungsoperator muss die vier wesentlichen Schritte befolgen: Selbstzuweisung prüfen, vorhandene Ressourcen freigeben, die neuen Daten kopieren und
*thiszurückgeben. Der Verschiebezuweisungsoperator sollte den Zeiger der Quelle übernehmen und die Quelle in einem gültigen leeren Zustand belassen (nullptr mit der Länge 0).- Einen privaten
main.cpp: Lies drei Zeichenketten aus der Eingabe ein (jeweils in einer eigenen Zeile). Erstelle dreiDynamicString-Objekte:str1mit der ersten Eingabe,str2mit der zweiten Eingabe undstr3mit der dritten Eingabe.Demonstriere deine Zuweisungsoperatoren:
- Gib den Anfangszustand aller drei Zeichenketten aus
- Verwende die Kopierzuweisung:
str1 = str2; - Gib str1 und str2 nach der Kopierzuweisung aus (beide sollten denselben Inhalt haben)
- Verwende die Verschiebezuweisung:
str2 = std::move(str3); - Gib str2 nach der Verschiebezuweisung und die Länge von str3 aus (sie sollte nach dem Verschieben 0 sein)
Ausgabeformat:
Initial: str1: <value> str2: <value> str3: <value> After copy (str1 = str2): str1: <value> str2: <value> After move (str2 = std::move(str3)): str2: <value> str3 length: 0
Verwende <cstring> für strlen und strcpy. Kennzeichne deinen Verschiebezuweisungsoperator mit noexcept. Binde <utility> in main.cpp für std::move ein. Vergiss die Header-Guards in deiner Header-Datei nicht.
Probier es selbst
#include <iostream>
#include <string>
#include <utility>
#include "DynamicString.h"
using namespace std;
int main() {
// Drei Strings von der Eingabe lesen
string input1, input2, input3;
getline(cin, input1);
getline(cin, input2);
getline(cin, input3);
// Drei DynamicString-Objekte erstellen
DynamicString str1(input1.c_str());
DynamicString str2(input2.c_str());
DynamicString str3(input3.c_str());
// TODO: Initialzustand aller drei Strings ausgeben
// Format:
// Initial:
// str1: <value>
// str2: <value>
// str3: <value>
// TODO: Kopierzuweisung verwenden: str1 = str2;
// TODO: str1 und str2 nach der Kopierzuweisung ausgeben
// Format:
// After copy (str1 = str2):
// str1: <value>
// str2: <value>
// TODO: Verschiebezuweisung verwenden: str2 = std::move(str3);
// TODO: str2 nach dem Verschieben und die Länge von str3 ausgeben
// Format:
// After move (str2 = std::move(str3)):
// str2: <value>
// str3 length: 0
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