Menu
CoddyTech

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 na podstawie innego obiektu tego samego typu, przekazujesz obiekt przez wartość lub zwracasz obiekt przez wartość.

Konstruktor kopiujący przyjmuje stałą referencję do obiektu tej samej klasy. Musi to być referencja: przekazanie other przez wartość samo wymagałoby kopii, co powodowałoby ponowne wywoływanie konstruktora kopiującego w nieskończoność. Jest const, ponieważ tworzenie kopii nigdy nie powinno zmieniać oryginału:

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ący

Jeśli nie zdefiniujesz konstruktora kopiującego, kompilator wygeneruje taki, który wykonuje płytką kopię — 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 wykonujący 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 niestandardowego konstruktora kopiującego oba obiekty wskazywałyby na tę samą pamięć. Gdy jeden z nich zostanie zniszczony, drugi będzie miał wiszący wskaźnik. Głęboka kopia przydziela nową pamięć i kopiuje rzeczywiste dane, zapewniając, że każdy obiekt ma własne zasoby.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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, i 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 zbiorem wiadomości za pomocą pamięci dynamicznej. Twoja klasa powinna mieć:
    • Prywatne składowe: wskaźnik na tablicę ciągów znaków do przechowywania wiadomości, capacity określające maksymalną liczbę wiadomości oraz count określające bieżącą liczbę wiadomości
    • Konstruktor sparametryzowany, 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, który zwalnia zaalokowaną pamięć i wyświetla "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ść pod podanym indeksem
  • main.cpp: Pokaż, że konstruktor kopiujący tworzy niezależną kopię. Odczytaj wartość pojemności i dwie wiadomości ze standardowego wejścia, a następnie:
    • Utwórz obiekt MessageLog o nazwie original z podaną pojemnością
    • Dodaj obie wiadomości do original
    • Utwórz kopię o nazwie backup za pomocą konstruktora kopiującego
    • Dodaj trzecią wiadomość "New message" tylko do original
    • Wyświetl "Original count: <count>"
    • Wyświetl "Backup count: <count>"
    • Wyświetl wszystkie wiadomości z backup, po jednej w każdym wierszu

Format danych wejściowych:

  • 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 oryginał 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() {
    // Wczytaj 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, po jednej w każdym wierszu

    return 0;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online