Menu
Coddy logo textTech

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

Jeś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.

challenge icon

Wyzwanie

Łatwy

Stwó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, capacity określające maksymalną liczbę wiadomości oraz count okreś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
  • 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 MessageLog o nazwie original z wczytaną 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
    • Wypisz "Original count: <count>"
    • Wypisz "Backup count: <count>"
    • Wypisz wszystkie wiadomości z backup, po jednej w każdym wierszu

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;
}
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