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 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-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 ein
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 String-Lä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 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 sie 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.
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 & 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