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.

const_cast conversion - 1

10:52

Öncelikle const_cast tür dönüştürme işleminde const bir değiskeni const' luktan çıkartıp değeri değiştirebilir hale getirmek icin kullanılmaz. Aşağıdaki örnek; en temel akla gelen const kullanım senaryosuna örnektir.


Ama kodumuz beklenen biçimde sonuçlanmayacak ve ival değişkeninin değeri sabit kalacaktır.

Gerçek kullanım senaryolarından ilki ise kötü bir tasarim biçimi olan const correctness' a uymayan fonksiyon tasarımında fonksiyon parametresi const olması gerekirken const yapılmayan bir fonksiyon için fonksiyon çağrısı için kullanılır.


İkinci kullanım biçimi ise sınıfın this pointer değerini const' luktan çıkartarak const olmayan sınıfın üyelerini değiştirme imkanıdır. Buna ilişkin örnek aşağıdaki gibidir:


Sınıfın setNumber() fonksiyonu const olduğu için this pointer değeri const olacak ve aşağıdaki gibi tür dönüşümü olmadan yapılan değer atama işlemi derleme hatasına neden olacaktır.


Sınıfın const üye fonksiyonu içinde atama işlemi yapılamayacağına ilişkin derleme hatası aşağıdaki gibidir:




static_cast conversion - 1

05:58

C++' da derleyicinin yaptığı otomotik tür dönüşümleri dışında 5 farklı tür dönüşümü vardır:
  • static_cast
  • const_cast
  • dynamic_cast
  • reinterpret_cast
  • geleneksel C style cast işlemi 
Bu yazımızda static_cast tür dönüşümünü anlatacağız. İlerleyen yazılarımızda sırala diğer tür dönüşümlerını anlatmaya devam edeceğiz. 

static_cast derleme zamanına ilişkin tür dönüşümleri için kullanılır.  Aşağıda benzer tür dönüşümü için kullanılabilecek 3 farklı senaryoyu içeren bir örnek bulunmaktadır. 

3 farklı tür dönüşüm örneği
  • 7. satırda doğrudan atama yapılmış ve hiçbir tür dönüşümü direktifi kullanılmamıştır. 
  • 8. satırda konu başlığını içeren tür dönüşümü.
  • 9. satırda ise geleneksel C tür dönüşümü yapılmış.
Bu kodu Microsoft compiler' ı ile derlerseniz karşınıza 7. satır için şöyle bir uyarı gelecektir.
"warning C4244: 'initializing': conversion from 'double' to 'int', possible loss of data" 

Note: Calıştığınız bazı projelerde gelecekte olası hataları önlemek adına compiler flag'lerinden tüm uyarıları error olarak ayarlanmış ise bu kodu derlemeniz mümkün olmayacaktır. 

Dolayısıyla türler arası bir geçiş yapacaksanız ve özellikle büyük kümeyi kapsayan bir türden küçük kümeye doğru bir dönüşim yapılacaksa compiler'a ben ne yaptığımı biliyorum direktifini vermek için tür dönüşümünü dilin verdiği olanaklar içinde en doğru şekilde belirtmek gerekir. Bu senaryoda tabiki en doğru tercih static_cast olacaktır. Aşağıdaki örnekte ise üç farklı tür dönüşümü için compilerın ürettiği makina kodu görülebilir.


Aşağıdaki örnekte ise çalışma zamanında static_cast işleminin nasıl hataya yol açacağına ilişkin bir örnek yer almaktadır. Fonksiyon upcast işlemi yapmakta ve üst sınıfının public bir fonksiyonunu çağrılmaktadır. Fakat ikinci fonksiyon çağrısınında program çökecektir. 


Bu nedenle her static_cast işlemi derlendi ise çalışma zamanında bir problemle karşılaşmayacağınız anlamına gelmez.  Aşağıdaki örnekte ise derleme hatası oluşturacak bir casting örneğini görmektesiniz.


Kodu derlemeye çalıştığımızda aşağıdaki gibi bir hata ile karşılacaksınız.


Peki bu örneği geleneksel C tür dönüşümü ile yapmaya çalışsaydık;


Kodumuz güvenli olmayacak şekilde derlenebilecekti. Aslında bu örnek ilerideki yazılarımızın konusu olacak neden geleneksel C tür dönüşümünü kullanmamamız gerektiğine bir örnektir. Peki static_cast işlemi tamamiyle compile time'e ilişkin mi yoksa runtime'e etki ediyor mu? Aşağıda iki farklı örnek üzerinden açıklamaya çalışalım:
  • static_cast<std::string>("Doctor")
  • static_cast<int>(2.6784)
İlk örnekte const char* türünden std::string türüne dönüşüm yapılacak ve std::string ctor cağrılması gerekecektir ki bu işlem runtime' de gerçekleşecektir. İkinci örnekte ise derleme zamanında gerçekleşecektir. Aşağıda ise yukarıdaki iki örneğin derleme sonucu oluşan makina kodunu görebilirsiniz.

Soru: Aşağıdaki programın derleme/çalışma sonucu ne olur?