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. 



C++ Style Guide | AAA (Almost Always Auto)


Modern C++'ile birlikte soldan sağa doğru bir bildirim stilinde değişme görülmeye başlanmıştır.Bu değişimin sebebi C++11 ile gelen auto anahtar sözcüğüdür. Bu değişim artık bildirimlerin sol tarafinda sadece auto' nun yer almasını ve diger tüm bildirimleri sağ tarafa aktarmak  şeklinde kısaca söylenebilir. Bu idiyomun genel kabul görmüş hali AAA (Almost Always Auto) olarak C++ dünyasına girmistir. Aşağıda geleneksel olarak yazılmış C++ kodunu görmekteyiz.


 Şimdi yukarıdaki kodu AAA idiyomuna göre değiştirelim.


Aşağıda ise daha kompleks kullanım senaryolarını görebilirsiniz.



Metaprogramming | basic implementation of std::is_integral

std::is_integral metaprogramming sırasında verinin bir türe ait olup olmadığına ilişkin kısıtlama yapılmak istendiğinde kullanılan bir araçtır.


Yukarıdaki 26 satırdaki ifadeden dolayı aşağıdaki gibi compile sürecinde static_assert hatasına neden olacaktır.


Bu satırı kaldırırsanız aşağıdaki gibi bir çıktı göreceksiniz.


Şimdi is_integral sınıfının temel olarak nasıl yazıldığını öğrenmek istersek aşağıdaki gibi bir implementasyon mümkündür.


Burada asıl kritik nokta is_integral_base sınıfı üzerinden olmaktadır. is_integral_base sınıfının integral veri türleri için template specialization yapılmakta ve bu sınıflar true_type türünden türetilmektedir.
Diğer her tür ise false_type türünden türetilmiştir. Bu sayede örneğin is_integral<bool> için is_integral_base<bool> için std::true_type türünden türetildiği için value değeri true dönecektir.


Yukarıda ise false_type/true_type türlerinin basit bir bildirimi görülmektedir. Beklenildiği gibi sınıfların üye elemanlarından biri value türüdür. Ve false_type için bu false, true_type için bu true olarak atama yapılmıştır.


Metaprogramming | CRTP (Curiously Recurring Template Pattern) – 1

C++ 'da sınıflara ait fonksiyonalarda virtual mekanizması kullanılırsa bu sayede dynamic polymorhism ya da çalışma zamanı çok biçimliliği olarak isimlendirilen Late binding mekanizması devreye girer. Compiler bunu için bir virtual table (vtable) oluşturur ve çalışma zamanında bu tabloya bakarak hangi fonksiyonun çağrılacağına karar verir. Aşağıda buna ilişkin bir örneği görmek mümkündür.


Aşağıda programın devamı olan caller fonksiyonu derleme zamanında hangi call fonksiyonunun çağrılacağını belirlememektedir. Bunu çalışma zamanında vtable'a bakarak karar verecektir.


Fakat Late binding'in tahmin edeceğiniz gibi program çalışma sırasında performans kaybına neden olacaktır. Peki bu mekanizmayı template programming'in bize verdiği imkanlar çerçevesinde nasıl yapabiliriz. Şimdi aşağıdaki örnekle yukarıdaki örnekte sağladığımız esnekliği performans kaybına neden olmadan gerçekleştirelim:


Base sınıfın template haline getirilmesi ve sınıfın call fonksiyonunun içinde yapılan static_cast işlemi 1. büyük nokta, 2. büyük nokta ise her türetilen sınıfın base'in kendisi türünden açılımı yapılacak olan sınıftan  türetilmesidir. Bu iki adım CRTP idiyomunun gerçekleşmesini sağlayan en önemli noktalardır.


Yukarıdaki iki örneği derleyip çalıştırırsanız ikisi için de programın benzer çıktılar verdiği görülebilir. Burada iki metodun da kendine göre avantajları ve dezavantajları bulunmaktadır. İleriki yazılarımızda bunlara ilişkin daha detaylı bilgiler paylaşılacaktır.

Soru : Base sınıfının call fonksiyonunu aşağıdaki gibi güncellersek programın derleme/çalışma sonucu nasıl degişir?


C++ Style Guide | Neden önemli?

Kodu yazanın dışında kodun bakımını yapan kişilerin de karşılarında temiz bir kod ile karşılaşabilmesi için okunabilir olması, koda bakan kişinin değişkenin türünü, hangi fonksiyonun neyi yaptığı ve nasıl yaptığını kolayca anlayabilmesi gerekmektedir. Bu konuda daha genel ve detaylı bilgi almak için "Linux Kernel Coding Style" veya "GNU Coding Standards" gibi standartlar incelenebilir.

Google C++ Style Guide


Yazılım geliştirme sürecinde kaliteyi (clean and bug-less code) korumak için development style guide denilen kod yazım standartlarına uymak büyük projeler için bir açıdan ihtiyaç hem de bir zorunluluk haline gelmektedir. Yeterince profesyonel olmayan bir yerde çalışıyorsanız C++ özelinde Google'ın C++ style guide'ını kullanmak kod kalıtenız adına ve  ileride çalışacağınız firmalara adaptasyon adına faydalı olacaktır. Özellikle yurtdışında çalışma planları olanlar için bu alışkanlığı başlangıçta edinmek iyi olacaktır. Yazımızda ve ileriki Style Guide yazılarımız C++ özelinde olacak olsa da genel olarak kod yazımı ile ilgili belirli kod standartlarını takip etmek uzun vadede kod kalıtenize onemli katkı yapacak ve ileride karşınıza çıkabilecek sorunlara karşı yardımcı olacaktır.  İleriki yazılarımızda:
  • forward decleration,
  • Comments,
  • output parametreleri,
  • decleration order,
  • exception and noexcept,
  • inline fonksiyonlar,
  • namespace kavramı ve dosyaların isimlendirilme biçimi,
  • AAA,
  • class member and inline fonksiyonlar
  • Macar notasyonu ve diğer notasyonlar
  • ...
konularından bahsedilecektir.

dynamic_cast conversion - 1

14:26

dynamic_cast tür dönüşümünde 'dynamic' sözcüğü, calışma zamanına karşılık gelmektedir. Program çalışma zamanında bir türden başka bir türe dönüşüm yapılmak istendiğinde kullanılan tür dönüştürme operatörüdür. Dinamik  tür dönüşümünde dönüştürülmek istenen türün polymorphic olması gerekmektedır. Bunun için de en azından bir virtual fonksiyonu olması gerekmektedir.


Yukarıdaki örnekte Base sınıfının hiç bir virtual fonksiyonu olmadığı için dynamic_cast işlemi aşağıdaki gibi bir hata ile karşılaşılacaktır.


cast işleminin çalışabilmesi için yukarıdaki örnek için Base sınıfına bir tane sanal fonksiyon eklenmiştir.


Aşağıdaki örnekte ise Foo ve Bar sınıfları Base sınıfından türemiş olsa da Foo ile Bar doğrudan birbirlerine dönüştürülemez. Dolayısıyla aşağıdaki örnekte dynamic_cast işlemi referansa cast edilmeye çalışılırsa başarısızlık durumunda nasıl bir senaryoyla karşılaşacağımız görülmektedir.


Başarısız bir cast işlemi std::bad_cast exception'ınına neden olacaktır.


Fakat adrese cast edilmek isteniyorsa bu durumda başarısızlık durumunda nullptr geri dönülecektir. Dolayısızla exception mekanizmasını programınıza dahil etmek istemiyorsanız pointer bazlı dönüşüm yapmak daha mantıklı olacaktır. 


Programın çıktısına bakarsanız cast işlemine gerek kalmadan her şey kontrol altına alınabilmektedir.


Son olarak dynamic_cast'in başka bir kullanım senaryosuda downcasting yapılarak üst türün alt türden olup olmadığının testini yapmaktır. Fakat bu amaç için virtual fonksiyon bulundurmak performans açısından istenmeyen bir durum olduğu için dynamic_cast yerine visitor pattern kullanılmaktadır. bu kullanım senaryosunu bir sonraki yazımızda anlatacağız.