RAII (Resource Acquisition Is Initialization idiom) - 1

Aşağıda gördüğümüz kod geleneksel kaynak erişim ve kaynakların iade edilmesine ilişkin bir örnek senaryodur. Burda 1 numaralı alanda kaynaklar ediniliyor sonra bu kaynaklarla belirli işlemler gerçekleştiriliyor ve ardından 2 numaralı alanda kaynaklar iade ediliyor.  Burada 2 tane senaryoya dikkat çekmek gerekiyor. Birincisi yazılım geliştiricisinin kaynaklarının iadesini UNUTMASI, ikincisi ise kaynakları iade etmeden exceptiona neden olunacak bir işlemin gerçekleşmesi ya da return edilmesi gibi durumlar. İşte bu noktada RAII devreye giriyor ve bu problemlerin çözümüne ilişkin bir senaryoyla karşımıza çıkıyor.


RAII idiyomu diğer bir değişle Resource Acquisition Is Initialization C++' ta popüler bir kaynak yönetim şekli. C++'ta sınıflara ilişkin nesneler yaratılırken constructor ile hayata gelir ve objenin ömrü bittiğinde destructor çağrılarak alınan kaynaklar teslim edilir. Çoğunlukla nesneler local scope içerisinde yaratılırlar ve scope'tan çıkıldığında (normal akışında, return veya exception) ömürleri de biter. Fakat bazı nesneler ise new anahtar kelimesi  ile oluşturulur ki heap'de dinamik hafıza alanında yer tahsis edilerek oluşturulur. Ve delete anahtar kelimesi ile destructor çağrısı yapılarak kaynaklar iade edilir. Bu yaşam döngüsü RAII idiyomunun dışındadır. RAII ismi aslında idiyoma doğru bir karşılık gelen ifade değildir. Aslında idiyomun anlatmak istediği aşağıdaki gibi bir isimlendirme ile daha doğru olarak karşılanacaktır.


Artık isimlendirme bu şekilde yapıldığı için RAII'yi kullanmaya devam edeceğiz ama idiyomu doğru anlamak içinse yukarıdaki isimlendirmeleri de bir kenara not edelim.

RAII'nin faydaları:

  • Exception Safety: exception safe bir kaynak yönetimi sağlamaya olanak sağlayacaktır.
  • Clean Code: kaynakların tahsis edilmesi ile iade edilmesi aynı sınıf içerisinde gerçekleşeceği için temiz bir kod yazımına olanak sağlayacaktır. 
  • Encapsulation: kaynakların yönetimi her sınıfın kendi içerisinde ve dışarıdan erişimi mümkün olmadan sağlandığı için bir encapsulation sağlayacaktır.


Yukarıdaki sınıfa bakarsak artık dosya kaynağına erişim constructor ile gerçekleşiyor, destructorda dosyanın kapatılarak kaynakların iadesini gerçekleştiriyor.


Bu sayede kaynağa erişim sağlayan kodun kaynak yönetimi encapsulation edilmiş oluyor ve kullanıcı tarafta temiz bir kod yazılmasını, dışarıdan kaynak yönetilmesine ihtiyaç kalmadan gerçekleştirilmiş oluyor. Bu konuya ilişkin bir sonraki yazımızda ise exception ve RAII ilişkine değinen yazımızı yazacağız.

RTTI | Run-Time Type Information - 1

RTTI, Run-Time Type Information'ın kısaltmasıdır. Çalışma zamanında tür bilgisinin elde edilmesi anlamına gelmektedir. Bunun için ilgili türlerin polimorfik olması gerekmektedir.  Bir türün polimorfik olması için en az bir tane kendisinde veya base türünde virtual bir metodun tanımlanmış olması gerekmektedir. Her sınıfın (objenin degil) kendine özgü bir vtable'ı bulunmaktadır. Basit olarak vtable yapısı aşağıdaki gibidir.

Tür bilgisi vtable içerisinde yer aldığından dolayı böyle bir yapısal ilişki - zorunluluk olmaktadır. type_info değeri aşağıda da görebileceginiz üzere vtable içerisindedir.

 C++ 'da RTTI mekanizmasının 3 farklı kullanım senaryosu vardir:

  • typeid operatörü
  • type_info sınıfı
  • dynamic_cast işlemi 

typeid ve type_info 

typeid operatörü bir nesnenin dinamik tipini öğrenmek için kullanılır. Ve typeid operatörü, bir type_info nesnesini const bir referans olarak döndürür. Daha sonra ihtiyaca göre bu nesneyi diğerleriyle karşılaştırabilir veya türün ismini yazdırabiliriz.


typeinfo ise bir sınıf türündendir. Ve genelde typeid operatörünün geri dönüş değerleri sınıfın == operatörü ile karşılaştırılabilir. Türlerin eşitliği için name karşılaştırmasına göre == karşılaştırması daha  çok tavsiye edilmektedir.


dynamic_cast 

Derleme zamanının aksine çalışma zamanında bir nesne hakkında type cast işlemi sayesinde tür hakkında bilgi almak için kullanılır. Polimorfik olmayan dillerde bu bilgiye gerek yoktur, çünkü tür her zaman derleme zamanında ve dolayısıyla çalışma zamanında bilinir. C++ gibi polimorfik türleri destekleyen bir dilde, türün derleme sırasında bilinmediği durumlar vardır. Bir pointer, temel sınıf türündeki bir nesneyi veya temel sınıftan türetilen herhangi bir sınıf türündeki bir nesneyi işaret edebilir. Bu nedenle RTTI ve dolayisiyla dynamic_cast ile çalışma zamanında tür hakkında bilgi alınmasına yardımcı olunur.


Yukarıdaki örnekte speak fonksiyonu runtime da gelen parametreye göre iki farklı biçimde davranmaktadır. Burada dynamic_cast'in RTTI mekanizması kullanarak bu işi gerçekleştirdiğini unutmayalım.

vtable layout

g++ icin -fdump-class-hierarchy flag değeri ile vtable (virtual function table) layout değerleri incelenebilir. Program derlenirse cpp dosyasi ile ayni isimde ama uzantı olarak .002t.class şekilde bir dosya oluşturulur. Içeriğine bakarsak :

c++filt aracı ile C++ symbolleri demangle edilirse asağıdaki gibi vtable'ın ikinci elemanı typeinfo 'ya ilişkin olduğu görülmektedir.

Note1 : type_info nesnelerinin copy constructor'ları yoktur. Bu nedenle bir containerda saklamak gibi yöntem doğrudan mümkün değildir. Bunun için sarmalayıcı bir sınıf yazarak mümkün hale gelebilmektedir.

Note2: Normalde RTTI, -fno-rtti flag değeri ile disable edilebilir. RTTI in en temel dezavantajlarından biri çalıştırılabilir kodun boyutunun büyümesidir. RTTI yalnızca belirli türler için devre dışı bırakmak/etkinleştirmek mümkün değildir. Etkinleştirildiğinde ise, RTTI hem yerleşik hem de polimorfik olmayan kullanıcı tanımlı türler için otomatik olarak oluşturulur. Bu da calistirilabilir kodun boyutunun cok büyümesine neden olmaktadır.



Metaprogramming | Variadic Templates & Fold Expressions - 1

C++11 değişken parametreli şablonları desteklemektedir. Dile gelen bu araçla birlikte hem sınıf şablonları (class templates) hem de fonksiyon şablonları (function templates) artık istenen sayıda parametreye sahip olabiliyor. Değişken sayıda parametreye "parametre paketi" (parameter pack) deniyor. Bir şablon parametre listesinde typename anahtar sözcüklerini izleyen üç nokta atomundan (ellipsis) sonra gelen isim, söz konusu şablon parametresinin sıfır ya da daha fazla sayıda türe karşılık geldiğine işaret etmektedir. Bu syntax'ta "typename… Types" şablon parametre paketi (template parameter pack), "Types… values" ise fonksiyon parametre paketi (function parameter pack) olarak isimlendirilmektedir. C++17'den önce variadic template yazılırken recursive bir yapıda yazma metodolojisi izleniyordu. Aşağıdaki örnekte görüleceği üzere ikinci printer() fonksiyonu recursive çağrı yapacak şekilde yazılmıştır.


Yukarıda tek parametreli bir template specialization yapıldığını görebilirsiniz. Toplamda iki tane fonksiyona ihtiyaç duyulmuştur. Peki C++17'den sonra durum nedir?


Yukarıda ilk dikkatimizi çeken nokta tek bir fonksiyonla işin halledilmiş olması. Peki bu nasıl gerçekleşti? Aşağıda fold (katlı ifadelerin açılımı) expression 'ların açılımı görülmektedir.


Burada belirleyici nokta operandların hangi sıraya göre yapılacağıdır. 
  • (… op pack)                 ---->   ((pack1 op pack2 op …) op packN
  • (init op … op pack)   ---->   (((init op pack1) op pack2) op …) op packN
  • (pack op …)                 ---->    pack1 op (… op (packN-1 op packN))
Aşağıda genelleştirilmiş şekilde ifade edildiği ve bir örnek üzerinde açıklandığını görebilirsiniz.


Aşağıda ise fold expression ifadelerin nasıl bir container içine yerleştirilebileceğine ilişkin örneği bulabilirsiniz.


Aşağıda ise C++17 öncesi variadic bir toplama fonksiyonunun yazılışı görülmektedir.


fold expression ile aynı fonksiyonun yazılışı aşağıdaki gibidir.


Soru1: aşağıdaki örnekte std::forward kullanılmasının bize ne gibi avanatajı veya dezavantajı vardır?


Soru2: Aşağıdaki kodun çıktısı ne olur?




Metaprogramming | basic implementation of std::enable_if and SFINAE - 1

std::enable_if C++11 ile hayatımıza giren bir özellik ve Metaprogramming yapılırken SFINAE olarak bilinen idiyomun en önemli kullanım senaryosunu oluşturmaktadır. std::enable_if'in olası implementasyonu aşağıdaki gibidir.


Burada kritik nokta birinci template için sınıfın içinde hiç bir bildirim yapılmazken 2. ise template partial specialization olarak isimlendirilen bir özelleştirilmiş şablon sınıfta typedef bildirimi yapılmış olmasıdır. Birinci şablon parametresi true olursa derleyici kod üretimi için bu şablonu kullanacak. Diğer durumlarda ilkini kullanacaktır. Peki bu bize nasıl bir kullanım olanağı sağlıyor? Aşağıdaki örneği inceleyelim:


Yukarıda göreceğiniz üzere T türü int olursa; enable_if'in template 1. parametresi true olarak açılacak, 2. fonksiyon için false olarak açılacak. Yani implemetasyonda görüleceği üzere type bildirimi olmayan sınıfla ilişkilendirilecek ve doğal olarak failure durumu oluşacak. Bir işlev şablonu derleyici açısından şablon tür parametresinin ne olduğunu bilmek/çıkarım yapmak zorundadır. Şablon tür parametresinin olduğu ya açıkça belirtilmesi gerekir ya da derleyici tarafından bir çıkarım yapabiliyor olması gerekir. Bu anlama işlemi aşıldıktan sonra derleyici şablonda şablon tür parametresinin bulunduğu yerlerde gerçek tür bilgisini kullanmaya başlar. Örneğin T türünün geçtiği her yerde int türünü kullanarak yerine koyma (substituition) işlemi yapar. Bu yer değiştirme işleminde bir başarısızlık olursa (substitution failure) derleyici bu durumu hemen bir sentaks hatası olarak değerlendirip derleme hatası olarak işlemi sonlandırmaz. Önce diğer şablon işlev yüklemelerine de (overloads) bakmak zorundadır. Eğer bir başka yükleme işlev çağrısı için geçerli olursa derleyici bu yüklemeyi seçer ve sentaks hatası oluşmaz. Bu şekilde ilk failure'u error olarak ele almayıp işleme devam edip başarılı olması durumlarına Substitution Failure Is Not An Error denilmektedir. Sonuç olarak yukarıdaki örneğimizde programnımız doğru bir şekilde derlenecek ve ekranda "Integral" yazısı görülecektir. 


Yukarıdaki örnekte görüleceği üzere enable_if'i daha basite indirgeyerek anlatmak istersek bizim için compile time template switch diyebiliriz. Belirli şartlar sağlanırsa ilgili fonksiyonun çağrılıp çağrılmamasına karar verme mekanizması diyebiliriz. Ve yukarıdaki örnek için bizim fonksiyonumuz sadece integral türler için çalışacak şekilde yazılmış fakat main içinde bu fonksiyonu double türüyle kullanmaya çalıştığımızda hata verecektir.


Yukarıdaki örnekte ise geri dönüş türü üzerinden enable_if'in kullanılmasına bir örnek verilmiştir. Sınıfın 2 tane check fonksiyonu Car sınıfı hangi türden template açılım yapıldı ise açılım yapılan türün V6Engine türünde ise true dönen fonksiyona açılım yapılacak diğer durumlarda false'a geri dönen fonksiyonun açılımı yapılacak şekilde özelleştirilmiştir.

ADL (Argument Dependent Lookup) - 1

14:35 ,

Compiler isim araması yaparken bulunduğu isim alanı ve global isim alanı dışında fonksiyon parametresine bağlı olarak bir arama daha yapmaktadir.


Yukaridaki en temel Argument Dependent Lookup örneğinde görebileceginiz üzere normalde alert() fonksiyonu baska bir isim alanı içerisinde olsa da fonksiyonun parametresi ADL namespace içerisinde oldugu için compiler buraya da bakacak ve sonuç olarak fonksiyon uygun fonksiyon çağrısını yapabilecektir.


Yukarida ise global alanda ve ADL namespace'i icerisinde aynı imzalı fonksiyonlar bulunduğu için  compiler ikilemde kalacak ve asağıdaki gibi derleme hatası verecektir.
 

Yukarıdaki hatayı düzeltmek için fonksiyon çağrısını aşağıdaki gibi yapmak mümkündür.


Şimdi ADL'nin daha kompleks problemlerini inceleyelim:


Yukaridaki örnekte ADL devreye girer ve namespace içerisindeki foo fonksiyonunu çağırır diye tahmin yürütsekte ifade geçerli olmayacak ve compıler bar'ı sadece int türünden bir değişkebn olarak görecek ve aşagıdaki gibi hata ile karşılaşırız. 


Asağıda ise beklentilerin tersine sonuclanan ikinci örneği görmek mümkün. Normalde Mercedes sınıfı içerisindeki foo çağrısı doğrudan sınıfın üye fonksiyonu olan foo() 'nun üzerinden yapılmaya çalışacak ve bunun karşılığı olarak parametreli çağrı yapıldığı için hata verecektir. 


Compiler 2 nolu fonksiyon cağrısını yapmak isteyecek 1 nolu fonkiyonu dikkate almayacaktır. Bu nedenle aşağıdaki gibi derleme hatası oluşacaktır. 


Asağıda ise beklentilerin tersine sonuçlanacak olan üçüncü örneği görmek mümkündür. Burada bar fonksiyonu içerisinde extern fonksiyon bildirimi yapılmıştır.


 Ve bunun sonucu olarak 2. örnektekine benzer bir hata ile karşılaşılmıştır.


Eğer extern bildirimi yapılmazsa global foo() fonksiyonunun bulunması derleme hatasına neden olmayacak ve derleme hatasız sonuçlanacaktır.


C++ Idioms | Attorney Client Idiom - 1


C++ için önemli idiomlardan biri de Attorney Client Idiom olarak isimlendirilen idiomdur. Temel olarak nesne yönelimli programlamanın önemli ilkelerinden birisi olan encapsulation'ı bozabilecek durumları ortadan kaldırmak için geliştirilmistir. Encapsulation ilkesinin temeli verinin bütünlüğünü bozabilecek tüm fonksiyonların veya üyelerinin dışarıdan erişime kapalı olmasını sağlamak üzerinedir. Bunun icin sınıfın private bölümü kullanılmaktadır. Bu sayede sınıfın dışarıya sunduğu interface ile implementation kısmı birbirinden ayrılmış olmaktadır. Fakat hayatta her şey siyah/beyaz olmadığı gibi burada da benzer bir durum vardır. Bazen sınıfın private bölümüne sınıfın kendi kodlarının dışında dışarıdan erişilebilmesi durumu bazı problemler için en iyi çözüm haline gelebilmektedir. Bunun için C++ dilinin bize sunduğu friend'lik kavramı üzerinden bu sağlanabilmektedir. C++ 'da bir sınıf bir global işleve, başka bir sınıfın bir üye işlevine ya da başka bir sınıfın tüm işlevlerine bir arkadaşlık (friend) bildirimi ile, kendi private bölümüne erişim hakkı vermektedir. C++’ta friend bildirimiyle, yalnızca sınıfın belirli private öğelerine erişim hakkı vermek gibi bir özellik şimdilik mümkün değil. Eğer bir sınıfa arkadaşlık verirseniz sınıfınızın tüm private öğelerini o sınıfın erişimine açmış oluyoruz. Peki biz bu problemi nasil aşacağız? Ayrıca friend'lik iki sınıf arasındaki bağımlılığı (coupling) arttıran bir durum. Bu da SOLID prensiplerine uymayan bir durum. Sınıfınızın private bölümünde değişiklik yaptığınızda arkadaşlık verdiğiniz sınıfların kodlarında da bakım/değişiklik yapılması gerekmetedir. Bu da istenmeyen bir durumdur. Arkadaşlık vermek en makul çözümse olabilse de bu noktada coupling'i azaltmak için sınıfın private öğelerinin tümünü erişime açmak yerine yalnızca bu ihtiyaca konu private öğeleri erişime açmak daha iyi bir çözüm olacaktir. Bunun için C++ dünyasında kullanılan çözüm iki sınıf arasında bir 3. sınıf oluşturmak ve iki sınıf arasındaki ilişkiyi bu 3. sınıf üzerinden yapmak olacaktir. Iste bu noktada kabul gören çözüm olan Attorney Client Idiyom 'unun nasıl yapıldığına geçmek en mantıklısı olacaktır.


Yukarıdaki örnek için Engine sınıfının m_id ve m_hashValue olarak iki üye elemanı bulunmakta ve bir nedenle bunlardan sadece birini dışarıya açmak istemekteyiz. Bu noktada friend' lik vermek dışında başka bir çözümümüz bulunmadığını düşünelim. Işte bu durumda yukarıdaki gibi vekil bir sınıf (CarAttorney) yaratıp Engine sınıfın friend 'liğini bu sınıfa vererek, gerçek sınıfa ise friendliği bu vekil sınıftan verilerek arada bir katman oluşturulur. Ve hedeflenen sınıf sadece bizim CarAttorney sınıfında implemente ettiğimiz static işlevler dışında verinin bütünlüğünü bozacak hiç bir duruma izin verilmemiş olur.


Arkadaşlık bildirimleri taban sınıflardan kalıtım yoluyla türemiş sınıflara geçmez. Bu ne demek? Taban sınıfın arkadaşlık verdiği Helper sınıfı yukarıdaki örnekte CarAttorney sınıfı türemiş sınıfın (V6Engine) private bölümüne erişmesi mümkün değildir. Ancak taban sınıf tarafından arkadaşlık verilen bir sınıf, taban sınıfın private bölümünde bildirilen bir virtual işlevi taban sınıf göstericisi/referansı ile çağırdığında, dynamic polymorphism gereği türemiş sınıfın virtual işlevinin çağrılması mümkündür. Bu sayede yukarıdaki örnekte BMW sınıfının printHashValue fonksiyonu CarAttorney sınıfı üzerinden yapılan çağrıda türemiş sınıfın ilgili fonksiyonuna doğrudan erişim yetkisi olmamasına rağmen CarAttorney tarafından çağrılacaktır.



CTAD (Class Template Argument Deduction)

CTAD (Class Template Argument Deduction); template fonksiyonlara ilişkin deduction guide line gibi sınıflar için de benzer şekilde C++17 ile standartlara gelmiştir. Bu sayede sınıflara ilişkin doğrudan explicit olarak belirtmeye gerek kalmadan bu görevi de compiler' a havale edilmesi sağlanmıştır. Ve daha kısa ve anlaşılır kodlar yazılmasına olanak sağlanmıştır. CTAD 'ın daha anlaşılır olması için aşağıdaki iki örneğe bakalım.

C++11 oncesi
C++17 öncesi
Yukarıdaki örnek için C++17 öncesi class template argument 'lerini doğrudan (explicit) olarak bildirmemiz gerekmektedir. Aksi halde hata ile karşılaşılmaktadır. Fakat C++17 ile bu problemler çözülmüştür. 

C++17 sonrası
Şimdi tam bir template class yazalım ve C++ 'in  11 - 14 ve 17 standartlarına göre derlemeye çalışalım.


Yukaridaki örneği C++11 ve C++14 ile derlemeye çalıştığımız zaman aşağıdaki gibi bir hata ile karşılaşılmaktadır. Fakat C++17 ile derledigimiz zaman ise sorunsuz derlenebilmektedir.


C++17 öncesi için yukaridaki problem aşağıdaki şekilde kodu güncelledigimiz zaman ortadan kalkmaktadir.


Fakat C++17 öncesi için de farklı bir workaround bulunmaktadır. Örnegin make_pair fonksiyonu ile yukarıdaki 17. satırdaki işlemi make_pair ile explicit olarak tür belirtmeden yapmak mümkündür.