Aşağıdaki yazının aslı, auto_ptr for incomplete types, ilk olarak ACCU'nun Silikon Vadisi kolunun yayın organı ACCent'in Şubat 2002 sayısında bir basım hatası nedeniyle başka birisinin adıyla yayınlanmıştır. Yazarın adı, Ali Çehreli yerine Allan Kelly olarak çıkmıştır :o)

Şubat 2002 tarihinde yine Ali Çehreli tarafından Türkçe'ye çevrilmiştir.

Yazıyla ilgili her türlü fikrinizi ve önerinizi lütfen Ali'ye bildirin: acehreli@yahoo.com . Teşekkürler...


Tanımlanmamış türlerle auto_ptr

Bu konuyu, derleyicinin konuyla ilgili olarak verdiği ilk uyarıdan ("deletion of pointer to incomplete type") beri ertelemekteydim. Bu uyarıdan kurtulmak için aldığım pahalı yöntem de, derleyicinin sınıfın tanımını görmesini sağlamak için sınıfın bildiri kütüğünü içermekti. [0]

Yalın işaretçiler yerine akıllı işaretçiler kullanmanın bir çok yararı vardır. Öncelikle, bellek kayıplarına yol açan hatalar önlenmiş olur. Ayrıca, bulundukları kapsam alanlarının sonunda bozulurlarken, akıllı işaretçiler sahip oldukları kaynaklarını geri verecekleri için, nesnelerin yaşam süreçlerini de denetlemiş oluruz.

Aykırı durum hatalarıyla çalışabilen kod yazabilmek için de akıllı işaretçiler kullanılması gerekir. Birden fazla kaynağa sahip olan sınıfların kurucuları, aykırı durum hataları atıldığı zaman bu kaynakların hepsini birden geri veremeyebilirler. Bunu önlemek için, kaynak denetiminin akıllı işaretçiler gibi yalnızca bu işlerden sorumlu sınıflara taşınmaları gerekir. [1]

Bazı işler için daha uygun başka akıllı işaretçiler olduğu halde, ben burada C++ standardının tanımladığı tek akıllı işaretçi olan auto_ptr'ı kullanacağım. [2]

Aşağıdaki sınıf, sahip olduğu kaynağı denetlemek için auto_ptr kullanıyor. Ne var ki, bu sınıfın bildiri kütüğü bir kullanıcı kütüğü içine alındığında, derleyici, henüz tanımı görünmeyen 'Yardimci' sınıfının bozucusunun çağrılmakta olduğunu belirten bir uyarı verecektir.

// s.h - Sınıfın bildiri kütüğü (arabirim tanımı)

#include <memory>

class Yardimci;        // tanımlanmamış sınıfın bildirilmesi

class S
{
    std::auto_ptr<Yardimci> p_;    // tanımsız sınıfı kullanan auto_ptr
    S(S const &);                  // kopyalama kurucusunu tanımlamayacağız
    S & operator= (S const &);     // atama işlecini de tanımlamayacağız

public:

     S();
     /* ... */ 
};

Kopyalama kurucusundan ve atama işlecinden başka, sınıfın bozucusunun da tanımlanmadığına dikkat edin. C++ standardının söylediğine göre, bozucusu tanımlanmayan her sınıf için derleyicinin örtülü olarak bir bozucu oluşturması, ve bu bozucunun sınıfın bütün öğelerinin bozucularını çağırması gerekir. [3]

Şimdi, S sınıfının kullanıcısı olan başka bir kütüğün (kullanici.cpp), s.h kütüğünü içerdiğini kabul edelim. Derleyici tarafından oluşturulan bozucu, aşağıdakinin eşdeğeri olurdu:

// kullanici.cpp - S sınıfının bir kullanıcısı 

/* ... */ 

// Not: Gösterim amacıyla yazılmış sözde kod! 

// Derleyicinin oluşturduğu bozucu örtülüdür; kütükte böyle yer almaz! 

inline S::~S()
{
   p_.std::~auto_ptr<Yardimci>()
}

Yukarıdaki tek satırın yerine auto_ptr'ın bozucusunun tek satırlık içeriğini koyarsak, derleyicinin oluşturduğu bozucunun aslında şöyle olduğu görülür:

// Burada ptr_, std::auto_ptr'ın öğesi olan yalın işaretçiyi belirtiyor.

inline S::~S()
{     
    delete ptr_; 
}

Bu güzel; auto_ptr zaten sahip olduğu nesneyi silmekle yükümlüdür. Şimdi bir adım daha ileri giderek 'delete' ifadesini de açarsak şu koda varırız:

inline S::~S() 
{     
    ptr_->~Yardimci(); // tanımlanmamış türün bozucusunu çağır
    __release__(ptr_); // bu, belleği geri veren hayali bir kitaplık işlevi olsun 
}

Sorun, derleyici tarafından oluşturulan bu bozucunun, tanımlanmamış bir türün bozucusunu çağırmak zorunda kalması. Böyle bir durumla karşılaştığımda uyguladığım yöntem, tanımı gözükmeyen sınıfın bildiri kütüğünü içererek derleyiciyi mutlu etmekti. Yani s.h kütüğünü şöyle değiştiriyordum:

// s.h - Sınıfın bildiri kütüğü (arabirim tanımı) 

#include <memory> 
#include "yardimci.h" // <--- yeni eklenen satır
// Not: Yukarıdaki satır nedeniyle bütün kullanıcılar 
// artık yardimci.h'ye bağımlılar!

// Tanımlanmamış sınıfın burada bildirilmesine artık gerek yok.

class S 
{ 
    /* Burası öncekiyle aynı; sınıfın tanımında bir değişiklik yapmıyoruz */ 
};

Ancak bunu yaparken, yalın işaretçiler yerine auto_ptr'lar kullanmanın kütükler arası bağımlılıkları bu şekilde artırmasının ne kadar büyük bir şanssızlık olduğunu da düşünüyordum. [4]

Bu konu, üyesi olduğum bir çalışma grubunun bir toplantısında yeniden gündeme geldi.[5] O toplantıda Allan, "Cheshire kedisi" olarak da adlandırılan bir yordamda Herb Sutter'ın auto_ptr kullanmasını sorguluyordu. auto_ptr'ın o yordamda gerçekten gerekli olup olmadığının yanında, başka ilginç bir soru daha oluştu: nasıl oluyordu da Herb Sutter auto_ptr'ı tanımlanmamış bir türle birlikte kullanabiliyordu?

Bu sorunun çözümü, yıllarca ertelenmeyi gerektirmeyecek kadar basitmiş! Sonradan anladığımıza göre çözüm, S sınıfının bozucusunu gerekli olmasa bile bildirmek, ve tanımlama kütüğünde boş olarak tanımlamak:

// s.h /* ... */ 

class S 
{ 
    /* ... */ 
    ~S();              // bozucuyu bildiriyoruz 
    /* ... */ 
};


// s.cpp 

#include "yardimci.h" 

/* ... */ 

S::~S() 
{}          // boş olarak tanımlıyoruz 

/* ... */

Bozucunun açıkça bildirildiğini gören derleyici de artık onu kendisi oluşturmaya kalkmıyor, ve sonuçta da kullanıcı kodu, bozucuyu doğrudan çağırmamış oluyor. Yardimci'nın bir kullanıcısı olduğu için s.cpp kütüğü, yardimci.h kütüğünü içererek zaten tanımını görmektedir. Hatta s.cpp kütüğü, bu yordamda Yardimci sınıfını kullanan zaten tek kütüktür.

S sınıfının boş bozucu işlevini s.h bildiri kütüğünde tanımlamanın da sorunu çözmediğine ayrıca dikkat edin. Wayne'in de güzelce belirttiği gibi önemli olan, bozucunun nerede tanımlandığıdır.

Referanslar:

[0] Bu sorun ve çözümüyle ilgili daha ayrıntılı bilgi için Alan Griffiths'in Ending with the grin adlı yazısını okuyabilirsiniz.

[1] Aykırı durumlarla ilgili bilgi için, Herb Sutter'ın konuyu derinine incelediği "Exceptional C++" adlı kitabına başvurabilirsiniz.

[2] Başka akıllı işaretçiler (smart pointer) için başvurulacak bir kaynak boost.org'dur.

[3] ISO/IEC 14882, Programming Languages -- C++, Section 12.4 Destructors

[4] Kütük bağımlılıkları ile ilgili geniş bilgi için John Lakos'un belki de bu konunun tek kaynağı olan "Large Scale C++ Software Design" adlı kitabına bakınız.

[5] O toplantıda bulunanlar; Ali Çehreli, Allan Kelly, Walter Vannini, ve Wayne Vucenic'ti.