Shallow copy (powierzchowne kopiowanie) – kopiuje tylko wartości pól obiektu, w tym wskaźniki, ale nie duplikuje pamięci przydzielonej dynamicznie. W konsekwencji oba obiekty wskazują na ten sam obszar pamięci. Może to prowadzić do błędów przy modyfikacji danych lub zwalnianiu pamięci.
Deep copy (głębokie kopiowanie) – tworzy pełną kopię wszystkich zasobów, na które wskazuje obiekt, aby każdy obiekt miał swoją kopię danych.
Kiedy potrzebny jest deep copy:
Przykład implementacji deep copy:
class ArrayHolder { public: ArrayHolder(size_t size) : sz(size), data(new int[size]) {} ~ArrayHolder() { delete[] data; } // Konstructor kopiujący głęboko ArrayHolder(const ArrayHolder& other) : sz(other.sz), data(new int[other.sz]) { std::copy(other.data, other.data + other.sz, data); } // Przypisanie kopiujące głęboko ArrayHolder& operator=(const ArrayHolder& other) { if (this != &other) { delete[] data; sz = other.sz; data = new int[sz]; std::copy(other.data, other.data + sz, data); } return *this; } private: size_t sz; int* data; };
Pytanie: Czy konstruktor kopiujący będzie poprawnie działał, jeśli po prostu skopiujemy wartość wskaźnika?
Odpowiedź: Nie, prowadzi to do podwójnego zwolnienia pamięci przy zniszczeniu obu obiektów. Należy wdrożyć głębokie kopiowanie dla prawidłowego zarządzania zasobami.
Historia
W jednym z projektów do przekazywania tablic między modułami używano obiektów klasy, w której domyślny konstruktor kopiujący kopiował tylko wskaźnik (shallow copy). W rezultacie, po wyjściu tymczasowego obiektu z zakresu widoczności tablica została zniszczona, a główny obiekt pozostał z "wiszącym" wskaźnikiem, co prowadziło do nieokreślonego zachowania i awarii aplikacji.
Historia
W bibliotece do pracy z obrazami wielokrotnie kopiowano obiekty zawierające dynamicznie przydzielone bufory. Z powodu braku deep copy po zwolnieniu pamięci w jednym obiekcie inny uzyskiwał dostęp do zwolnionego obszaru. Przejawiała się to jako uszkodzenia danych oraz awarie przy usuwaniu obiektu.
Historia
Podczas integracji przestarzałego kodu z nowoczesnymi standardami w starych klasach nie zaimplementowano ani konstruktora kopiującego, ani operatora przypisania. Użycie tych klas z kontenerami biblioteki standardowej prowadziło do nieoczekiwanych awarii, ponieważ kontenery kopiowały obiekty powierzchownie.