Sovraccarico dell'operatore di assegnazione
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 44 di 104.
L’operatore di assegnazione per copia (=) viene chiamato quando assegni un oggetto esistente a un altro. A differenza del costruttore di copia (che crea un nuovo oggetto), l’operatore di assegnazione lavora con un oggetto che esiste già e potrebbe avere risorse da liberare prima.
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. Self-assignment check
delete[] data; // 2. Clean up existing resource
size = other.size; // 3. Copy the data
data = new int[size];
for (size_t i = 0; i < size; ++i)
data[i] = other.data[i];
}
return *this; // 4. Return *this
}
};Il controllo di autoassegnazione (this != &other) è fondamentale. Senza di esso, b = b; eliminerebbe i dati prima di provare a copiarli, causando un comportamento indefinito.
L'operatore restituisce *this per riferimento, consentendo concatenazioni come a = b = c;. Questo corrisponde al comportamento dei tipi integrati.
Per le classi che gestiscono risorse, spesso implementerai sia gli operatori di assegnazione per copia sia quelli di assegnazione per spostamento:
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data; // Libera la risorsa corrente
data = other.data; // Acquisisci la risorsa
size = other.size;
other.data = nullptr; // Lascia l'oggetto sorgente in uno stato valido
other.size = 0;
}
return *this;
}L'operatore di assegnazione per spostamento trasferisce la proprietà invece di copiare, rendendo operazioni come vec[0] = createBuffer(); molto più efficienti.
Sfida
FacileCreiamo una classe DynamicString che gestisce un array di caratteri allocato dinamicamente e gestisce correttamente l'assegnazione tra oggetti esistenti. Questo è uno scenario pratico in cui devi trasferire in modo sicuro i dati di una stringa da un oggetto a un altro senza perdite di memoria o puntatori pendenti.
Creerai due file per organizzare il codice:
DynamicString.h: definisci una classeDynamicStringche memorizza una stringa in stile C usando memoria dinamica. La tua classe deve gestire:- Un puntatore privato
char*per i dati della stringa - Un
size_tprivato per la lunghezza - Un costruttore che accetta un
const char*e crea una copia profonda - Un distruttore che libera correttamente la memoria allocata
- Un operatore di assegnazione per copia che esegue una copia profonda proteggendosi dall'autoassegnazione
- Un operatore di assegnazione per spostamento che trasferisce la proprietà in modo efficiente
- Un metodo
c_str()che restituisce la stringa interna (const) - Un metodo
length()che restituisce la lunghezza della stringa (const)
L'operatore di assegnazione per copia deve seguire i quattro passaggi essenziali: verificare l'autoassegnazione, liberare le risorse esistenti, copiare i nuovi dati e restituire
*this. L'operatore di assegnazione per spostamento deve sottrarre il puntatore alla sorgente e lasciare la sorgente in uno stato vuoto valido (nullptr con lunghezza 0).- Un puntatore privato
main.cpp: leggi tre stringhe dall'input (ciascuna su una riga separata). Crea tre oggettiDynamicString:str1con il primo input,str2con il secondo input estr3con il terzo input.Mostra il funzionamento degli operatori di assegnazione:
- Stampa lo stato iniziale di tutte e tre le stringhe
- Usa l'assegnazione per copia:
str1 = str2; - Stampa str1 e str2 dopo l'assegnazione per copia (entrambi devono avere lo stesso contenuto)
- Usa l'assegnazione per spostamento:
str2 = std::move(str3); - Stampa str2 dopo l'assegnazione per spostamento e la lunghezza di str3 (deve essere 0 dopo lo spostamento)
Formato dell'output:
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
Usa <cstring> per strlen e strcpy. Contrassegna l'operatore di assegnazione per spostamento come noexcept. Includi <utility> in main.cpp per std::move. Non dimenticare le protezioni dell'header nel file header.
Provalo tu
#include <iostream>
#include <string>
#include <utility>
#include "DynamicString.h"
using namespace std;
int main() {
// Leggi tre stringhe dall'input
string input1, input2, input3;
getline(cin, input1);
getline(cin, input2);
getline(cin, input3);
// Crea tre oggetti DynamicString
DynamicString str1(input1.c_str());
DynamicString str2(input2.c_str());
DynamicString str3(input3.c_str());
// TODO: Stampa lo stato iniziale di tutte e tre le stringhe
// Formato:
// Initial:
// str1: <value>
// str2: <value>
// str3: <value>
// TODO: Usa l'assegnazione per copia: str1 = str2;
// TODO: Stampa str1 e str2 dopo l'assegnazione per copia
// Formato:
// After copy (str1 = str2):
// str1: <value>
// str2: <value>
// TODO: Usa l'assegnazione per spostamento: str2 = std::move(str3);
// TODO: Stampa str2 dopo lo spostamento e la lunghezza di str3
// Formato:
// After move (str2 = std::move(str3)):
// str2: <value>
// str3 length: 0
return 0;
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online