Menu

C++ Yıkıcılar Açıklandı: ~SınıfAdı, RAII, Temizlik

Bir yıkıcı, bir nesne yok edildiğinde otomatik olarak çalışır. ~SınıfAdı() söz dizimini, ne zaman tetiklendiğini, kaynakları neden serbest bıraktığını ve Üçler/Beşler Kuralı'nı öğrenin.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Yıkıcı Nedir

Önceki sayfada yapıcıları gördünüz - bir nesne doğduğunda başlangıç durumunu kurmak için çalışan özel fonksiyonlar. Yıkıcı bunun ayna görüntüsüdür: bir nesne öldüğünde, ardından temizlik yapmak için çalışan özel bir fonksiyon.

Yıkıcıyı, sınıf adının önüne bir tilde (~) koyarak bildirirsiniz. Parametre almaz, hiçbir şey döndürmez ve bir sınıfın tam olarak bir tane yıkıcısı olabilir. Onu neredeyse hiçbir zaman elle çağırmazsınız - C++ doğru anda sizin yerinize çağırır.

Yıkıcı mesajının, main gövdesini bitirdikten sonra ama program çıkmadan önce yazdırıldığına dikkat edin. log kapanış parantezinde kapsamından çıktığında, C++ sizin yerinize ~Logger() fonksiyonunu çalıştırır.

Yıkıcılar Ne Zaman Çalışır

Tam zamanlama, nesnenin nerede yaşadığına bağlıdır:

  • Yığıt (yerel) nesneler, kapsamlarından çıktıklarında - bloğun kapanış } parantezinde - yok edilir.
  • Öbek (heap) nesneleri (new ile oluşturulanlar), delete çağırdığınızda yok edilir. delete'i unutursanız, yıkıcı hiç çalışmaz ve sızıntıya yol açarsınız.

Bu örnek farkı görünür kılıyor:

Nesneler, oluşturulma sıralarının tersine yok edilir. a ilk oluşturuldu, bu yüzden en son ölür. Nesneler birbirine bağımlı olduğunda bu LIFO (son giren, ilk çıkar) sırası önem kazanır.

Yıkıcılar Neden Önemlidir: RAII

Yıkıcıların asıl gücü, temizliği otomatik ve istisna güvenli hale getirmeleridir. Her kod yolunda bir kaynağı serbest bırakmayı hatırlamak yerine, serbest bırakmayı bir yıkıcıya koyarsınız ve onun çalışmasını dilin garanti etmesine izin verirsiniz. Bu desene RAII - Resource Acquisition Is Initialization (Kaynak Edinimi Başlatmadır) - denir ve modern C++'ın bel kemiğidir.

Burada bir sınıf, öbekteki bir tamponun sahibidir: yapıcıda yer ayırır ve yıkıcıda serbest bırakır, böylece çağıranlar new/delete'e hiç dokunmaz.

Temel fikir şudur: squares oluşturulduktan sonra bir istisna fırlatılsa bile, yığıt geri sarılır ve ~IntArray() yine de çalışır. RAII'yi bu denli güvenilir kılan işte bu garantidir - ve iyi C++ kodunda neden nadiren çıplak bir delete yazdığınızın nedeni budur.

Üçler Kuralı (ve Beşler)

Özel yıkıcısı olan bir sınıf neredeyse her zaman ham bir kaynağa sahiptir ve bu gizli bir tehlike yaratır. Derleyicinin ürettiği kopyalama yapıcısı ve kopyalama ataması sığ bir kopya yapar - işaret ettiği tamponu değil, işaretçinin kendisini kopyalar. Artık iki nesne aynı işaretçiyi tutar ve iki yıkıcı da onu delete eder, bu da çifte serbest bırakma (double-free) çökmesine neden olur.

IntArray a(5);
IntArray b = a;   // sığ kopya: a.data ve b.data AYNI işaretçidir
// kapsam sonunda: b'nin yıkıcısı tamponu serbest bırakır,
// sonra a'nın yıkıcısı onu TEKRAR serbest bırakır -> tanımsız davranış (çifte serbest bırakma)

Bu da Üçler Kuralı'na götürür: yıkıcı, kopyalama yapıcısı veya kopyalama atama operatöründen herhangi birini yazarsanız, neredeyse kesinlikle üçüne birden ihtiyacınız vardır. C++11 ve sonrasında bu kural, taşıma yapıcısı ve taşıma atamasını da ekleyerek Beşler Kuralı'na genişler.

Yine de daha da iyi bir kural var - Sıfır Kuralı: sınıfları, ham kaynakları hiç yönetmeyecek şekilde tasarlayın. Bunun yerine bir std::vector, bir std::string veya bir akıllı işaretçi tutun; derleyicinin ürettiği yıkıcı doğru olanı bedavaya yapsın.

Varsayılan olarak Sıfır Kuralı'na yönelin. Özel bir yıkıcıyı yalnızca hiçbir standart türün sizin için sarmalamadığı bir ham kaynağa gerçekten sahip olduğunuzda yazın.

Sanal Yıkıcılar

Bir nesneyi taban sınıf işaretçisi üzerinden sildiğinizde, yıkıcı virtual olmalıdır - aksi takdirde yalnızca taban kısım yok edilir ve türetilmiş kısım sızar. Bu, polimorfik koddaki en yaygın hatalardan biridir ve derleyici varsayılan olarak sizi bu konuda uyarmaz.

~Base üzerinde virtual olmadan, delete p yalnızca ~Base()'i çağırırdı - tanımsız davranış ve nesnenin Derived kısmı asla temizlenmez. Genel kural: sanal fonksiyonları olan herhangi bir sınıfın (polimorfik bir taban sınıfın) sanal bir yıkıcıya ihtiyacı vardır. Sınıf türetmeye başladığınızda bunun neden önemli olduğunu tam olarak göreceksiniz.

Yaygın Hatalar ve Tuzaklar

Birkaç tuzak neredeyse herkesi düşürür:

Eşleşmeyen new/delete. new[] ile yer ayırdıysanız delete[] ile serbest bırakın. new[]'i düz bir delete ile (veya tersi) karıştırmak tanımsız davranıştır.

Taban yıkıcıda virtual'ı unutmak. Yukarıda olduğu gibi, türetilmiş bir nesneyi sanal bir yıkıcı olmadan taban işaretçisi üzerinden silmek, türetilmiş kısmı sızdırır. Kalıtım için tasarlanmış bir sınıf yazıyorsanız, yıkıcıyı sanal yapın.

İstisnaların bir yıkıcıdan kaçmasına izin vermek. Yığıt geri sarılırken istisna fırlatan bir yıkıcı, programınızı sonlandırır. Modern C++'ta yıkıcılar örtük olarak noexcept'tir - temizlik kodunun istisna fırlatmasını engelleyin ya da istisnayı yıkıcının içinde yutun.

İhtiyacınız olmayan bir yıkıcı yazmak. Üyeleriniz zaten kendilerini temizliyorsa, boş bir ~SınıfAdı() {} gürültü ekler ve taşıma işlemlerini sessizce devre dışı bırakabilir. Temizlenecek bir şey yoksa, hiç yıkıcı yazmayın.

Sıradaki: Kalıtım

Artık bir nesnenin tam yaşam döngüsünü gördünüz - yapıcılar onu hayata getirir, yıkıcılar onu temizler ve virtual yıkıcılar, bir sınıf bir başkasının üzerine inşa edildiğinde bu temizliği doğru tutar. Bu son nokta, sıradaki büyük fikrin bir önizlemesidir: kalıtım, yani bir sınıfın başka bir sınıfın verilerini ve davranışını yeniden kullanıp genişlettiği yer. Sıradaki sayfa, bir sınıfı başka bir sınıftan nasıl türeteceğinizi, oluşturma ve yok etmenin hiyerarşi boyunca nasıl zincirlendiğini ve az önce öğrendiğiniz parçaların nasıl bir araya geldiğini gösteriyor.

Sıkça Sorulan Sorular

C++'ta yıkıcı nedir?

Yıkıcı, bir nesne yok edildiğinde - kapsamından çıktığında veya delete ettiğinizde - otomatik olarak çalışan, ~SınıfAdı() adlı özel bir üye fonksiyondur. Görevi temizliktir: belleği serbest bırakmak, dosyaları kapatmak veya nesnenin sahip olduğu herhangi bir kaynağı boşaltmak. Parametre almaz, dönüş türü yoktur ve bir sınıfın yalnızca bir tane yıkıcısı olabilir.

C++'ta bir yıkıcı ne zaman çalışır?

Yerel (yığıt) bir nesne için yıkıcı, nesne kapsamdan çıktığında, kapanış } parantezinde çalışır. new ile oluşturulan bir öbek (heap) nesnesi için ise delete çağırdığınızda çalışır. Üyeler ve taban sınıflar daha sonra, oluşturulma sırasının tersine, otomatik olarak yok edilir.

C++'ta her zaman bir yıkıcı yazmam gerekir mi?

Hayır. Sınıfınız yalnızca kendi temizliğini yapan üyeler içeriyorsa (std::string, std::vector veya akıllı işaretçiler gibi), derleyicinin ürettiği yıkıcı yeterlidir - kendiniz yazmayın. Yalnızca sınıfınız ham bir kaynağa sahip olduğunda - örneğin new ile alınmış bellek veya açık bir dosya tanıtıcısı gibi - özel bir yıkıcıya ihtiyaç duyarsınız.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA