ProgramlamaC++ Orta Seviye Geliştirici

C++'da dinamik bellek ile bir konteyner örneğinde shallow ve deep copy arasındaki farkları açıklayın. Derin kopyalamayı manuel olarak nasıl gerçekleştirebiliriz?

Hintsage yapay zeka asistanı ile mülakatları geçin

Cevap.

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.

Soru Geçmişi

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.

Sorun

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.

Çözüm

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:

  • Shallow copy işaretçileri kopyalar, deep copy yeni dinamik bellek örnekleri oluşturur.
  • Derin kopya için yapıcıda ve atama operatöründe kendi kopyalama mantığınızı uygulamanız gerekir.
  • Deep copy gerekliliğinin göz ardı edilmesi, yakalaması zor hatalara yol açar.

Tuzaklı Sorular.

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

Yaygın Hatalar ve Anti-Paternlere

  • Üçlü Kural'ı uygulama gerekliliğini göz ardı etmek.
  • İşaretçileri kopyalayarak nesnenin kopyası olduğunu düşünmek.
  • Yıkıcıda dinamik kaynakları serbest bırakmamak.
  • Sahip olduğu kaynaklara sahip sınıflar için shallow copy kullanmak.

Gerçek Hayat Örneği

Olumsuz Durum

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:

  • Hızlı çalışır (kopyalar yok).

Eksiler:

  • Çok zor tespit edilen çalışma zamanı hataları; double free/segfault olasılığı.

Olumlu Durum

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:

  • Güvenli kopyalama ve bellek serbest bırakma.
  • Kod bakımı ve genişletilebilirliği daha kolaydır.

Eksiler:

  • Biraz daha fazla kod ve bellek masrafı.
  • Birden fazla dinamik kaynağa sahip sınıflar için daha karmaşık.