Costruttore di copia
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 20 di 104.
Un costruttore di copia crea un nuovo oggetto come copia di un oggetto esistente. Viene chiamato quando inizializzi un oggetto a partire da un altro oggetto dello stesso tipo, passi un oggetto per valore o restituisci un oggetto per valore.
Il costruttore di copia accetta un riferimento const a un oggetto della stessa classe:
class Player {
std::string name;
int health;
public:
Player(std::string n, int h) : name(n), health(h) {}
// Costruttore di copia
Player(const Player& other) {
name = other.name;
health = other.health;
}
};
Player original("Hero", 100);
Player copy = original; // Costruttore di copia chiamato
Player another(original); // Chiama anche il costruttore di copiaSe non definisci un costruttore di copia, il compilatore ne genera uno che esegue una copia superficiale - copia direttamente il valore di ciascun membro. Questo funziona bene per i tipi semplici, ma causa problemi quando la tua classe gestisce memoria dinamica.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
// Costruttore di copia profonda
Buffer(const Buffer& other) : size(other.size) {
data = new int[size]; // Alloca nuova memoria
for (size_t i = 0; i < size; i++)
data[i] = other.data[i]; // Copia il contenuto
}
~Buffer() { delete[] data; }
};Senza un costruttore di copia personalizzato, entrambi gli oggetti punterebbero alla stessa memoria. Quando uno viene distrutto, l’altro avrebbe un puntatore pendente. Una copia profonda alloca nuova memoria e copia i dati effettivi, assicurando che ogni oggetto possieda le proprie risorse.
Sfida
FacileCostruiamo un sistema di cronologia dei messaggi che dimostri l'importanza della copia profonda quando la tua classe gestisce memoria dinamica. Creerai una classe MessageLog che memorizza i messaggi in un array allocato dinamicamente e implementerai un costruttore di copia corretto per assicurarti che ogni copia abbia la propria memoria indipendente.
Creerai due file per organizzare il codice:
MessageLog.h: definisci una classeMessageLogche gestisce una raccolta di messaggi usando la memoria dinamica. La tua classe dovrebbe avere:- Membri privati: un puntatore a un array di stringhe per memorizzare i messaggi, una
capacityper il numero massimo di messaggi e uncountper il numero attuale di messaggi - Un costruttore con parametri che accetta una capacità, alloca l'array nell'heap e inizializza count a 0
- Un costruttore di copia che esegue una copia profonda: alloca nuova memoria e copia tutti i messaggi dall'oggetto sorgente
- Un distruttore che libera la memoria allocata e stampa
"MessageLog destroyed" - Un metodo
addMessage(string msg)che aggiunge un messaggio se c'è spazio - Un metodo
getCount()che restituisce il conteggio attuale dei messaggi - Un metodo
getMessage(int index)che restituisce il messaggio all'indice specificato
- Membri privati: un puntatore a un array di stringhe per memorizzare i messaggi, una
main.cpp: dimostra che il tuo costruttore di copia crea una copia indipendente. Leggi un valore di capacità e due messaggi dall'input, poi:- Crea un
MessageLogchiamatooriginalcon la capacità specificata nell'input - Aggiungi entrambi i messaggi a
original - Crea una copia chiamata
backupusando il costruttore di copia - Aggiungi un terzo messaggio
"New message"solo aoriginal - Stampa
"Original count: <count>" - Stampa
"Backup count: <count>" - Stampa tutti i messaggi di
backup, uno per riga
- Crea un
Il formato dell'input sarà:
- Prima riga: capacità (intero)
- Seconda riga: primo messaggio (stringa)
- Terza riga: secondo messaggio (stringa)
Se la copia profonda è implementata correttamente, il backup dovrebbe avere 2 messaggi mentre l'originale ne ha 3: questo dimostra che hanno memoria indipendente. Senza un costruttore di copia corretto, entrambi condividerebbero lo stesso array e le modifiche apportate a uno influenzerebbero l'altro!
Includi il tuo file header in main.cpp usando #include "MessageLog.h".
Provalo tu
#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;
int main() {
// Leggi l'input
int capacity;
cin >> capacity;
cin.ignore();
string msg1, msg2;
getline(cin, msg1);
getline(cin, msg2);
// TODO: Crea un MessageLog chiamato 'original' con la capacità indicata nell'input
// TODO: Aggiungi entrambi i messaggi a original
// TODO: Crea una copia chiamata 'backup' usando il costruttore di copia
// TODO: Aggiungi "New message" solo a original
// TODO: Stampa "Original count: <count>"
// TODO: Stampa "Backup count: <count>"
// TODO: Stampa tutti i messaggi di backup, uno per riga
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