Konstruktor kopiujący oraz operator przypisania kopiującego są niezbędne do pracy z obiektami zarządzającymi zasobami (np. dynamiczną pamięcią). Domyślnie kompilator wygeneruje przypisanie kopiujące pole po polu, co jest niebezpieczne dla surowego wskaźnika:
class Buffer { public: Buffer(size_t size) { data = new int[size]; this->size = size; } ~Buffer() { delete[] data; } // Poprawna implementacja konstruktora kopiującego Buffer(const Buffer& other) : size(other.size) { data = new int[size]; std::copy(other.data, other.data + size, data); } // Poprawny operator przypisania 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; } private: int* data; size_t size; };
W przeciwnym razie podczas kopiowania dwóch obiektów jeden zwalnia pamięć, a drugi pozostaje z wieszającym wskaźnikiem (double free lub use after free).
Co się stanie, jeśli jawnie zadeklarujemy tylko konstruktor kopiujący, ale nie operator przypisania? Kiedy jest on potrzebny, a kiedy — nie?
Odpowiedź: Jeśli zadeklarowany jest tylko konstruktor kopiujący, ale nie zadeklarowany operator przypisania, kompilator wygeneruje operator przypisania domyślnie (kopiowanie bitowe), co jest niebezpieczne, jeśli obiekt zawiera dynamiczne zasoby, tzn. będzie niszczyć ten sam wskaźnik dwukrotnie.
W przypadku zarządzania pamięcią należy zaimplementować oba: zarówno konstruktor kopiujący, jak i operator przypisania, aby uniknąć błędów kopiowania/wycieków pamięci.
Historia
W serwerze multimedialnym przy kopiowaniu obiektu bufora użyto konstruktora kopiującego, ale zapomniano o operatorze przypisania. Kiedy jeden bufor przypisywano do drugiego, następowało podwójne zwolnienie pamięci podczas niszczenia obiektów. Błąd ujawnił się podczas testów obciążeniowych.
Historia
W projekcie logistyki transportowej operator przypisania domyślnie kopiował strukturę z wskaźnikiem na tablicę współrzędnych. Po usunięciu jednego obiektu drugi odwoływał się do zwolnionej pamięci, powodując błędy segmentacji.
Historia
W jednym z projektów z zasobami graficznymi zapomniano zaimplementować konstruktor kopiujący przy przekazywaniu obiektów między wątkami. Przekazywanie przez wartość prowadziło do shallow copy, a po zmianie obiektu w innym wątku dochodziło do uszkodzenia danych i zawieszenia programu.