curiously recurring template pattern – 1

Bildiğimiz gibi C++ dili sanal işlevler yoluyla çok biçimliliğe güçlü bir destek veriyor. Sanal işlevlerin kullanılması durumunda hangi işlevin çağrıldığı programın çalışma zamanında anlaşıldığından bu tür çokbiçimliliğe “dinamik çokbiçimlilik” ya da “çalışma zamanı çok biçimliliği” deniyor. Dinamik çokbiçimliliğin getirdiği ek maliyetler var. Sanal işlevlere yapılan çağrılarda derleyici tipik olarak şöyle bir kod üretiyor:
i) Sınıf nesnesinin içine bir gösterici veri öğesi gömülüyor. Sanal işleve bir çağrı yapıldığında bu göstericiden sanal işlev tablosu deneilen bir veri yapısının adresi elde ediliyor.
ii) Bu veri yapısından bir indis kullanarak çağrılacak işlevin adresi elde ediliyor. Böyle bir kod da iki kez içerik alma (dereferencing) maliyeti içeriyor.

Bellek kullanımı açısından baktığımızda da her sınıf nesnesi için bir göstericilik bellek alanı kullanılıyor. Ayrıca türetme hiyerarşisi içindeki her sınıf için sanal işlev tablosu olarak kullanılan veri yapısının konumlandırılacağı bellek alanı gerekiyor. Özellikle küçük veriler taşıyan küçük sınıf nesneleri için bu bellek maliyeti bazen istenmiyor.  Performans kritik uygulamalarda sanal işlevlerin faydalarını bir ölçüde bize sağlayacak ancak maliyeti azaltacak bazı başka çözümler var. İşte CRTP bunuı sağlayan örüntülerden (pattern) biri.

CRTP, C++‘ta sık karşımıza çıkan örüntülerden biri. Bu teknik 1980’li yıllarda  “F-bounded quantification” olarak isimlendirilmiş.  1995 yılında Jim Coplien örüntüye bu ismi vermiş. O tarihten bugüne örüntü bu isimle biliniyor.   Türkçeye kelime kelime çevirirsek “meraklıca yinelenen şablon örüntüsü” diyebiliriz. (demesek daha iyi olur gibi geliyor.)
Şimdi örüntümüze bakalım. Taban sınıf olarak kullanılacak bir sınıf şablonumuz var ve türemiş sınıfların her biri bu şablon sınıfın türemiş sınıf açılımından türetiliyor:

İlginç bir yapı değil mi? A sınıfını bir sınıf şablonu olan B‘nin, B<A> açılımından türetiyoruz. Dilin kurallarını çiğneyen hiç bir durum yok. Yukarıdaki örnekte şu nokta özellikle dikkatimizi çekmeli:

işlevi  Base sınıfından türetilen Der sınıfının bildiriminden önce (yani derleyicinin bu sınıfı bilmesinden önce) bildirilmesine karşın işlevin kodu Der sınıfının bilinmesinden sonra yazılacak.

Dikkatimizi çekmesi gereken ikinci bir nokta da taban sınıfın interface üye işlevi içinde yapılan

dönüşümü. Dilin kurallarına göre bu dönüşümün geçerli  olabilmesi için Base ve Derived sınıflarının aynı hiyerarşi içinde bulunması gerekiyor. Bu dönüşümle Base sınıfı türünden nesnenin adresini (Derived *) türüne dönüştürüyor ve tür dönüştürme işlecinden elde edilen adresle türemiş sınıfının implementation isimli işlevi çağırıyoruz. Derleyici bu durumda implementation ismini türemiş sınıfın isim alanında arıyor.

Bu teknik dinamik çok biçimliliğin maliyetinden kaçınarak ve çoğu zaman ilave esneklik kazandırarak sanal işlevlerin etkilerine benzer bir yapı oluşturuyor. CRTP‘nin bu özel kullanımı bazı kişiler tarafından “statik çokbiçimlilik” ya da “simüle edilmiş dinamik bağlama” olarak isimlendiriliyor. Windows’un ATL ve WTL kütüphanelerinde bu teknik yoğun olarak kullanılıyor. Tekniği anlamak için, sanal işlevleri olmayan bir sınıfı kafanızda canlandırın. Taban sınıf başka işlevleri kendi öğe işlevleri yoluyla çağırıyor. Taban sınıftan bir türetme yaptığımızda taban sınıfın tüm veri öğelerini ve öğe işlevlerini kalıtım yoluyla almış oluyoruz.

Şimdi bir örnek üzerinde dinamik çokbiçimliliği statik çok biçimliliğe dönüştürelim. Aşağıdaki gibi bir dinamik çokbiçimliliğe sahip sınıf hiyerarşimiz olsun:

Şimdi bu yapıyı CRTP örüntüsüne dönüştürelim:

Pet sınıfının private bölümünde tanımlanan

işlevi tür dönüştürme kodlarını daha kolay yazabilmemiz için oluşturduğumuz bir yardımcı yalnızca. C++’ın işlev şablonlarına ilişkin önemli bir kuralı da hatırlamanın tam zamanı. Bir işlev çağrısı yapılmadığı sürece derleyici bir işlev şablonundan ya da sınıf şablonunun üye işlevinden bir kod üretmeyecek.  getSound işlevlerinin kodunun üretilmesini tetikleyen main işlevi içinde yapılan çağrılar:

Şüphesiz iki yapının ayrı ayrı avantajları ve dezavantajları var. CRTP kullandığımız yapıda:
Ne taban sınıf nesneleri içine gömülü bir gösterici ne de çalışma zamanında bellekte sınıflar için oluşturulmuş bir işlev tablosu tutuluyor.
İşlev çağrısı doğrudan yapılıyor. Yani ki ayrı içerik alma işlemi yok.
Ayrıca dinamik çokbiçimlilikteki kurallardan farklı olarak taban sınıfın işlevinin imzasıyla türemiş sınıfın işlevinin imzasının aynı olması gerekmiyor.
Diğer taraftan taban sınıftan yapılan her bir türetme için hem taban sınıf için hem de türemiş sınıflar için farklı kodlar oluşturulduğundan statik çok biçimlilik sağlayan bu yapıda oluşturulan kod miktarı daha fazla olma eğiliminde.
Hangi işlevin çağrıldığı programın çalışma zamanında değil de derleme zamanında belli olduğu için bu yapının dinamik çok biçimliliğe göre daha kısıtlı bir kullanımı var.
Dinamik çokbiçimlilikteki gibi katı bir tür kontrolü yok.

CRTP‘nin kullanımı bu örneklerle sınırlı değil. Bu serinin diğer yazılarında CRTP‘nin farklı amaçlarla kullanımını da inceleyeceğiz.

Necati Ergin

C ve Sistem Programcıları Derneğinde eğitmen olarak çalışıyor.

Bunlar da ilginizi çekebilir

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Kod Eklemek İçin Okuyun