C++'da nesnelerin kopyalanma mekanizması yüzeysel kopyalama (shallow copy) ve derin kopyalama (deep copy) olarak ikiye ayrılır. Fark özellikle dinamik bellek kullanan sınıflar için önemlidir.
C++'da birçok veri yapısı dinamik bellek (new/delete) ile çalışır. Varsayılan olarak, derleyici kopyalama yapıcı ve atama operatörü oluşturarak byte seviyesinde kopyalama gerçekleştirir (shallow copy). Bu hızlıdır, ancak nesne dış kaynakları yönetiyorsa tehlikeli olabilir.
Shallow copy sadece dinamik olarak tahsis edilen kaynakların adreslerini kopyalar. Bir nesne silindiğinde bellek serbest bırakılacak ve diğer bir örnek "askıda kalan" (dangling) bir işaretçi ile kalacaktır. Sonuç olarak, double delete, bellek sızıntıları ve çökmeler meydana gelir.
Derin kopyalama, tüm dinamik kaynakların açık bir kopyasının oluşturulmasını gerektirir. Bunun için sınıfta her elemanın kopyasını sağlamak amacıyla kendi kopyalama yapıcınız ve atama operatörünüzü uygulamanız gerekir.
Dizi ile bir sınıf için kod örneği:
class DynArray { int* data; size_t size; public: DynArray(size_t n) : size(n), data(new int[n]) {} ~DynArray() { delete[] data; } // Derin kopyalama yapıcısı DynArray(const DynArray& other) : size(other.size), data(new int[other.size]) { for (size_t i = 0; i < size; ++i) data[i] = other.data[i]; } // Derin kopyalama atama operatörü DynArray& operator=(const DynArray& other) { if (this != &other) { delete[] data; size = other.size; data = new int[size]; for (size_t i = 0; i < size; ++i) data[i] = other.data[i]; } return *this; } };
Anahtar özellikler:
Derleyici, her zaman doğru bir kopyalama yapıcısı ve atama operatörü oluşturur, değil mi?
Cevap:
Yanlış. Dinamik kaynakları olan sınıflar için varsayılan kopyalama doğru değildir: her iki nesne de aynı kaynağı sahiplenmiş olur. Dış kaynaklara sahipken derin kopya açıkça uygulanmalıdır.
Sadece derin kopya yapıcı/ataması yazıldıysa, bir yıkıcıyı uygulamak gerekli mi?
Cevap:
Evet, aksi takdirde bellek sızıntısı oluşur: kullanıcı tanımlı kopya yapıcıda belleği serbest bırakırsanız, ancak bir yıkıcı uygulamazsanız, nesnelerin yok edilmesinde bellek serbest bırakılmayacaktır.
std::vector işaretçileri tutabilir mi ve kopyalama yapıldığında neden bellek sızıntıları meydana gelebilir?
Cevap:
Evet, std::vector rahatlıkla işaretçileri tutar. Bu tür bir std::vector kopyalandığında, işaretçilerin kendileri kopyalanır, onların işaret ettiği nesneler değil. Bu bir shallow copy'dir: eğer tüm içeriğin derin kopyasını almak isteniyorsa, her nesneyi manuel olarak kopyalamak ve bağımsız belleğe yerleştirmek gerekecektir.
Örnek:
std::vector<int*> v1; v1.push_back(new int(42)); std::vector<int*> v2 = v1; // İşaretçiler kopyalanır, *int değil
Bir programcı, kopyalama yapıcısını/atama operatörünü yeniden tanımlamadan bir dizi sarmalayıcı sınıfı uygular. Sonuç olarak, her iki nesne de aynı belleği sahiplenir, birinin yok edilmesi diğerine erişim sırasında çökme meydana getirir.
Artılar:
Eksiler:
Bir geliştirici derin kopyalamayı uygular: dizi içeriği kopyalanır, kendi yıkıcısı ve kendini atamadan koruma ile birlikte bir atama operatörü vardır.
Artılar:
Eksiler: