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 Ressourcen hat, 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 Prüfung auf Selbstzuweisung (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 Operationen wie vec[0] = createBuffer(); viel effizienter werden.
Aufgabe
EinfachLassen Sie uns eine DynamicString-Klasse erstellen, die ein dynamisch zugewiesenes Zeichenarray verwaltet und Zuweisungen zwischen bestehenden Objekten ordnungsgemäß handhabt. Dies ist ein praktisches Szenario, in dem Sie String-Daten sicher von einem Objekt auf ein anderes übertragen müssen, ohne Speicherlecks oder hängende Zeiger zu verursachen.
Sie werden zwei Dateien erstellen, um Ihren Code zu organisieren:
DynamicString.h: Definieren Sie eineDynamicString-Klasse, die einen C-Style-String unter Verwendung von dynamischem Speicher speichert. Ihre Klasse sollte Folgendes verwalten:- Einen privaten
char*-Zeiger für die String-Daten - Ein privates
size_tfür die Länge - Einen Konstruktor, der einen
const char*entgegennimmt und eine tiefe Kopie erstellt - Einen Destruktor, der den zugewiesenen 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 den internen String zurückgibt (const) - Eine
length()-Methode, die die Stringlänge zurückgibt (const)
Ihr Kopierzuweisungsoperator muss die vier wesentlichen Schritte befolgen: Prüfung auf Selbstzuweisung, Bereinigung vorhandener Ressourcen, Kopieren der neuen Daten und Rückgabe von
*this. Der Verschiebezuweisungsoperator sollte den Zeiger der Quelle übernehmen ("stehlen") und die Quelle in einem gültigen leeren Zustand hinterlassen (nullptr mit Länge 0).- Einen privaten
main.cpp: Lesen Sie drei Strings von der Eingabe ein (jeder in einer separaten Zeile). Erstellen Sie dreiDynamicString-Objekte:str1mit der ersten Eingabe,str2mit der zweiten Eingabe undstr3mit der dritten Eingabe.Demonstrieren Sie Ihre Zuweisungsoperatoren:
- Geben Sie den Anfangszustand aller drei Strings aus
- Verwenden Sie die Kopierzuweisung:
str1 = str2; - Geben Sie str1 und str2 nach der Kopierzuweisung aus (beide sollten den gleichen Inhalt haben)
- Verwenden Sie die Verschiebezuweisung:
str2 = std::move(str3); - Geben Sie str2 nach der Verschiebezuweisung und die Länge von str3 aus (sollte 0 sein, nachdem verschoben wurde)
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
Verwenden Sie <cstring> für strlen und strcpy. Markieren Sie Ihren Verschiebezuweisungsoperator als noexcept. Binden Sie <utility> in main.cpp für std::move ein. Vergessen Sie nicht die Header-Guards in Ihrer Header-Datei.
Spickzettel
Der Kopierzuweisungsoperator (=) weist ein bestehendes Objekt einem anderen zu. Er muss die Bereinigung bestehender Ressourcen verarbeiten, bevor neue Daten kopiert werden.
Struktur des Kopierzuweisungsoperators
ClassName& operator=(const ClassName& other) {
if (this != &other) { // 1. Self-assignment check
// 2. Clean up existing resources
// 3. Copy data from other
}
return *this; // 4. Return *this
}Beispiel mit dynamischem Speicher
class Buffer {
int* data;
size_t size;
public:
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data; // Clean up existing resource
size = other.size;
data = new int[size];
for (size_t i = 0; i < size; ++i)
data[i] = other.data[i];
}
return *this;
}
};Die Prüfung auf Selbstzuweisung (this != &other) verhindert das Löschen von Daten vor dem Kopieren in Fällen wie b = b;.
Die Rückgabe von *this als Referenz ermöglicht Verkettungen: a = b = c;.
Verschiebezuweisungsoperator
Der Verschiebezuweisungsoperator überträgt den Besitz, anstatt zu kopieren, was die Effizienz verbessert:
ClassName& operator=(ClassName&& other) noexcept {
if (this != &other) {
// Clean up current resource
// Steal the resource from other
// Leave other in valid state (nullptr, 0, etc.)
}
return *this;
}Beispiel Verschiebezuweisung
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data; // Steal the resource
size = other.size;
other.data = nullptr; // Leave source valid
other.size = 0;
}
return *this;
}Markieren Sie die Verschiebezuweisung als noexcept für eine optimale Performance mit Standard-Containern.
Probier es selbst
#include <iostream>
#include <string>
#include <utility>
#include "DynamicString.h"
using namespace std;
int main() {
// Lies drei Strings von der Eingabe ein
string input1, input2, input3;
getline(cin, input1);
getline(cin, input2);
getline(cin, input3);
// Erstelle drei DynamicString-Objekte
DynamicString str1(input1.c_str());
DynamicString str2(input2.c_str());
DynamicString str3(input3.c_str());
// TODO: Gib den Anfangszustand aller drei Strings aus
// Format:
// Initial:
// str1: <value>
// str2: <value>
// str3: <value>
// TODO: Verwende die Kopierzuweisung: str1 = str2;
// TODO: Gib str1 und str2 nach der Kopierzuweisung aus
// Format:
// After copy (str1 = str2):
// str1: <value>
// str2: <value>
// TODO: Verwende die Verschiebezuweisung: str2 = std::move(str3);
// TODO: Gib str2 nach dem Verschieben und die Länge von str3 aus
// 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 & 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