programowanieProgramista C++

Wyjaśnij różnicę między shallow copy a deep copy na przykładzie kontenera z dynamiczną pamięcią. Jak poprawnie zaimplementować głębokie kopiowanie?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Shallow copy (płytkie kopiowanie) kopiuje tylko wartości członków klasy, w tym wskaźniki — ale nie kopiuje danych wskazywanych przez te wskaźniki. Deep copy (głębokie kopiowanie) tworzy nowe kopie danych znajdujących się pod wskaźnikami, co zapobiega współdzieleniu pamięci i podwójnemu zwalnianiu.

Przykład:

class Buffer { public: Buffer(size_t size) : size(size), data(new int[size]) {} // Shallow copy (błędne) Buffer(const Buffer& other) : size(other.size), data(other.data) {} // Deep copy (poprawne) Buffer& operator=(const Buffer& other) { if (this != &other) { delete[] data; size = other.size; data = new int[size]; std::copy(other.data, other.data + size, data); } return *this; } ~Buffer() { delete[] data; } private: size_t size; int* data; };

Pytanie z haczykiem.

Jeśli klasa zawiera wskaźnik do dynamicznej tablicy, dlaczego nie wystarczy skopiować konstruktora kopiującego i operatora= z domyślnymi implementacjami?

Poprawna odpowiedź:

Kopiowanie domyślne realizuje płytkie kopiowanie. Dwa obiekty będą wskazywać na tę samą tablicę, a przy niszczeniu nastąpi podwójna próba zwolnienia tej samej pamięci (double free), co prowadzi do awarii.

Przykłady rzeczywistych błędów z powodu braku znajomości niuansów tematu.


Historia W dużej aplikacji serwerowej zaimplementowano własną klasę Image, która zarządzała dynamicznym buforem pikseli. Programiści nie zaimplementowali głębokiego kopiowania, a podczas przekazywania obiektów Image przez wartość między wątkami dochodziło do podwójnego zwalniania pamięci, prowadzącego do awarii co kilka dni.


Historia W projekcie studenckim użyto kontenera Field, który zawierał wskaźnik do dynamicznie przydzielonej tablicy komórek. Podczas zmiany rozmiaru tablicy konstruktor kopiujący kopiował tylko wskaźnik, a oba kontenery działały na tym samym obszarze pamięci, co prowadziło do niespójnych danych i trudnych do zlokalizowania błędów.


Historia W starym kodzie C++ silnika graficznego zaimplementowano klasę Texture bez własnego destruktora i operatora przypisania, co prowadziło do wycieków pamięci przy kopiowaniu i przypisywaniu obiektów tymczasowych tekstur.