Konstruktor kopiujący
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 20 z 104.
Konstruktor kopiujący tworzy nowy obiekt jako kopię istniejącego obiektu. Jest wywoływany, gdy inicjalizujesz obiekt innym obiektem tego samego typu, przekazujesz obiekt przez wartość lub zwracasz obiekt przez wartość.
Konstruktor kopiujący przyjmuje stałą referencję do obiektu tej samej klasy:
class Player {
std::string name;
int health;
public:
Player(std::string n, int h) : name(n), health(h) {}
// Konstruktor kopiujący
Player(const Player& other) {
name = other.name;
health = other.health;
}
};
Player original("Hero", 100);
Player copy = original; // Wywołano konstruktor kopiujący
Player another(original); // Również wywołuje konstruktor kopiującyJeśli nie zdefiniujesz konstruktora kopiującego, kompilator wygeneruje taki, który wykonuje płytkie kopiowanie — kopiuje bezpośrednio wartość każdego składowego. Działa to dobrze w przypadku prostych typów, ale powoduje problemy, gdy Twoja klasa zarządza pamięcią dynamiczną.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
// Konstruktor kopiujący z głęboką kopią
Buffer(const Buffer& other) : size(other.size) {
data = new int[size]; // Przydziel nową pamięć
for (size_t i = 0; i < size; i++)
data[i] = other.data[i]; // Skopiuj zawartość
}
~Buffer() { delete[] data; }
};Bez własnego konstruktora kopiującego oba obiekty wskazywałyby na tę samą pamięć. Gdy jeden z nich zostanie zniszczony, drugi miałby wiszący wskaźnik. Głęboka kopia przydziela nową pamięć i kopiuje rzeczywiste dane, dzięki czemu każdy obiekt jest właścicielem własnych zasobów.
Wyzwanie
ŁatwyStwórzmy system historii wiadomości, który pokazuje, jak ważne jest głębokie kopiowanie, gdy klasa zarządza pamięcią dynamiczną. Utworzysz klasę MessageLog, która przechowuje wiadomości w dynamicznie alokowanej tablicy, oraz zaimplementujesz odpowiedni konstruktor kopiujący, aby każda kopia miała własną, niezależną pamięć.
Utworzysz dwa pliki, aby uporządkować swój kod:
MessageLog.h: Zdefiniuj klasęMessageLog, która zarządza kolekcją wiadomości przy użyciu pamięci dynamicznej. Twoja klasa powinna zawierać:- Prywatne składowe: wskaźnik do tablicy ciągów znaków przechowującej wiadomości,
capacityokreślające maksymalną liczbę wiadomości orazcountokreślające bieżącą liczbę wiadomości - Konstruktor parametryzowany, który przyjmuje pojemność, alokuje tablicę na stercie i inicjalizuje count wartością 0
- Konstruktor kopiujący wykonujący głębokie kopiowanie: alokujący nową pamięć i kopiujący wszystkie wiadomości z obiektu źródłowego
- Destruktor zwalniający zaalokowaną pamięć i wypisujący
"MessageLog destroyed" - Metodę
addMessage(string msg), która dodaje wiadomość, jeśli jest miejsce - Metodę
getCount(), która zwraca bieżącą liczbę wiadomości - Metodę
getMessage(int index), która zwraca wiadomość o podanym indeksie
- Prywatne składowe: wskaźnik do tablicy ciągów znaków przechowującej wiadomości,
main.cpp: Pokaż, że konstruktor kopiujący tworzy niezależną kopię. Wczytaj wartość pojemności i dwie wiadomości z wejścia, a następnie:- Utwórz obiekt
MessageLogo nazwieoriginalz wczytaną pojemnością - Dodaj obie wiadomości do
original - Utwórz kopię o nazwie
backupza pomocą konstruktora kopiującego - Dodaj trzecią wiadomość
"New message"tylko dooriginal - Wypisz
"Original count: <count>" - Wypisz
"Backup count: <count>" - Wypisz wszystkie wiadomości z
backup, po jednej w każdym wierszu
- Utwórz obiekt
Format wejściowy:
- Pierwszy wiersz: pojemność (liczba całkowita)
- Drugi wiersz: pierwsza wiadomość (ciąg znaków)
- Trzeci wiersz: druga wiadomość (ciąg znaków)
Jeśli głębokie kopiowanie jest zaimplementowane poprawnie, kopia zapasowa powinna zawierać 2 wiadomości, a obiekt oryginalny 3: to dowodzi, że mają niezależną pamięć. Bez odpowiedniego konstruktora kopiującego oba obiekty współdzieliłyby tę samą tablicę, a zmiany w jednym wpływałyby na drugi!
Dołącz plik nagłówkowy w main.cpp za pomocą #include "MessageLog.h".
Spróbuj swoich sił
#include <iostream>
#include <string>
#include "MessageLog.h"
using namespace std;
int main() {
// Odczytaj dane wejściowe
int capacity;
cin >> capacity;
cin.ignore();
string msg1, msg2;
getline(cin, msg1);
getline(cin, msg2);
// TODO: Utwórz obiekt MessageLog o nazwie 'original' z pojemnością podaną na wejściu
// TODO: Dodaj obie wiadomości do original
// TODO: Utwórz kopię o nazwie 'backup' za pomocą konstruktora kopiującego
// TODO: Dodaj "New message" tylko do original
// TODO: Wypisz "Original count: <count>"
// TODO: Wypisz "Backup count: <count>"
// TODO: Wypisz wszystkie wiadomości z backup, każdą w osobnym wierszu
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online