The Rule of Three / Five or Six

08:16
C++11'den önce bu kural Rule of Three olarak ele alınmış ve C++11'le gelen taşıma (move) semantiği ile kurala ilişkin işlev sayısı artmıştır. Bu kural bir sınıf tanımı içinde birbiriyle ilişkili toplamda 6  özel işleve ilişkindir. Bunlar;
  1. default constructor
  2. copy constructor                        //rule of three
  3. copy assignment operator   //rule of three
  4. destructor                                   //rule of three
  5. move constructor                           //rule of five
  6. move assignment operator       //rule of five
Bu kuralla bağlantılı olarak Rule of Zero başlıklı yazıda anlattığımız gibi eğer rule of zero kuralı uygulanamıyorsa; bu yukarıda listelenen bazı fonksiyonları yazdığımız anlamına gelir ki bu noktada artık Rule of Five kuralının uygulanmasının daha doğru olacağını varsayabiliriz. Ve yazdığımız sınıfa ait nesneler kopyalanabilir olacak mı? ya da taşınabilir olacak mı? sorusunun cevabına bağlı olarak bazı fonksiyonlar tanımlamalı ya da =delete edilmesi gerekmektedir. Ayrıca rule of five kuralını uygulamazsak ciddi bir C++ kodlama eforunu gerektirecek bir yola girmemize sebep olacaktır. 

Bu eforlardan birisi bizim yazdığımız özel fonksiyonlarla birlikte derleyicinin neleri yazıp yazmayacağı ya da delete edeceği sorunsalıdır. Derleyici bu noktada bizim sınıf tanımı içerisinde yazdığımız özel fonksiyonlara göre hareket etmektedir. Aşağıda bununla ilgili kural matrisini görebiliriz.
user-declared & implicitly-declared matrisi
Yukarıdaki matristen bir kaç okuma yapmak istersek; Bir sınıfın sonlandırıcı fonksiyonu bildirilirse derleyici sınıfın taşıma fonksiyonlarıyla (move members) ilgili bildirim yapmaz bunun dışındaki tüm özel işlevlerin varsayılan edileceği matrisin 5. satırında görülebilir.
  • Car() = default;
  • Car(const Car &) = default;
  • Car &operator=(const Car &) = default;
Bir sınıfın taşıyan atama fonksiyonu bildirilirse derleyici sınıfın varsayılan kurucu fonksiyonunu ve sınıfın sonlandırıcı fonksiyonunu varsayılan yapar. Bu durumda yine sınıfın kopyalayan işlevleri derleyici tarafından delete edileceği matrisin 9. satırında görülebilir.

  • Car() = default;
  • ~Car() = default; 
  • Car(const Car &) = delete;
  • Car &operator=(const Car &) = delete;
Bir sınıfın kopyalayan kurucu fonksiyonun bildirirse derleyici sınıfın kopyalayan atama işlevini ve sonlandırıcı işlevlerini varsayılan edileceği matrisin 6. satırında görülebilir.

  • ~Car() = default;
  • Car &operator=(const Car &) = default;
Ve bu noktada derleyicinin varsayılan olarak yazıp yazmayacağı yada delete edip etmeyeceği  konusunun ne kadar karmaşık olduğu ve Rule of Five kuralının biraz da bu karmaşıklığın ve olası hataların önüne geçmek için ortaya çıktığını rahatlıkla görebilirsiniz.

Örnek - 1 : Kaynak Kod
Özetle bu kural eğer birini bildiriyorsanız bu özel fonksiyonların tamamı ile ilgili bildirimleri gerçekleştirin ve karmaşadan kurtulun sonucuna varabiliriz. Yukarıdaki örnekte tam bu kurala uygun şekilde gerçekleştirilmiştir.
Örnek - 1 : Çıktı

Note:

  • Derleyici için neler bildirim olarak niteleniyor dersek? Aşağıdaki 4 durum da kullanıcı bildirimi olarak nitelendirilmektedir.
    X() {}    //user-declared

    X();     //user-declared

    X() = default;    //user-declared

    X() = delete;    //user-declared
  • Sadece kopyalanabilen (Copyable Objects) objeler söz konusu olduğunda
    • Otomatik kaynak yönetimi kullanılıyorsa: The Rule of Zero.
    • Manuel kaynak yönetimi kullanılıyorsa: The Rule of Three.
  • Hep kopyalanabilen hem taşınabilen (Moveable Objects) objeler söz konusu olduğunda
    • Otomatik kaynak yönetimi kullanılıyorsa: The Rule of Zero.
    • Manuel kaynak yönetimi kullanılıyorsa: The Rule of Five.
  • Taşınamayan Nesneler (Unmoveable Objects
    Bir nesnenin taşınarak hayata başlanmasını istemiyorsak move constructor'ın delete edilmesi gerekmektedir.

    Örnek: Car(Car&& source) = delete;

    Bir nesneye taşıma yoluyla atama yapılmasını istemiyorsak move assignment operator'ın delete edilmesi gerekmektedir.

    Örnek: Car& operator=(Car&& rhs) = delete;

  • Kopyalanamayan Nesneler (Uncopyable Objects)
    Bir nesnenin kopyalanarak hayata başlanmasını istemiyorsak copy constructor'ın delete edilmesi gerekmektedir.

    Örnek: Car(const Car& kSource) = delete;

    Bir nesneye kopyalanarak atama yapılmasını istemiyorsak copy assignment operator'ün delete edilmesi gerekmektedir.

    Örnek: Car& operator=(const Car& kRhs) = delete;

Algorithm | any_of, all_of, none_of algoritmaları


Algoritmaların template bildirimleri
C++11 ile gelen bu üç algoritma lambdalarla birlikte çok faydalı biçimlerde kullanılabilmektedir.  Üç algoritma içinde şablondan üretilecek fonksiyonun ilk iki parametresi  kontrol edilmesi gereken aralığa ilişkin başlangıç ve bitiş itearator değerleri, üçüncü parametre ise tek parametreli bir sınayıcı (predicate) 'dir. Şimdi sırasıyla çalışma mantıklarını irdeleyelim:

Algorithm | any_of

std::any_of algoritmasıyla bir aralık (range) içindeki değerlerden herhangi birinin bir koşulu sağlayıp sağlamadığını kontrol edilmektedir. std::any_of'un geri dönüş değeri bool türündendir. Herhangi biri koşulu sağlıyorsa "true" döner.
STL Test Algoritmalarından any_of örneği
Yukarıdaki örnekte string türünden bir vector container elemanlarının içerisinden herhangi birinin "Mukhtar" elemanına eşit olup olmadığı kontrol edilmiş ve bir tanesi koşulu sağladığı için true dönmüştür. ikinci kontrolde ise herhangi bir üye'nin uzunluğu 11'den fazla mı kontrolü yapılmış ve koşul sağlandığı için true geri dönmüştür.

Algorithm | all_of

std::all_of algoritması ile bir aralık (range) içindeki tüm değerlerin bir koşulu sağlayıp sağlamadığını kontrol edilmektedir. std::all_of'un geri dönüş değeri bool türündendir. Tamamı koşulu sağlıyorsa "true" döner.
STL Test Algoritmalarından all_of örneği
Yukarıdaki örnekte string türünden bir vector container elemanlarının hepsinin uzunluğu 6'dan büyük mü olup olmadığı kontrolü yapılmış ve hepsi koşulu sağladığı için true dönmüştür. ikinci kontrolde ise hepsinin uzunluğu 11'den büyük mü olup olmadığı kontrolü yapılmış ve hepsi koşulu sağlamadığı için false dönmüştür.

Algorithm | none_of

std::none_of algoritması bir aralık (range) içindeki tüm değerlerin verilen bir koşulu sağlamadığını kontrol etmektedir. std::none_of'un geri dönüş değeri bool türündendir. Tamamı koşulu sağlamıyorsa "true" döner. Bu noktada none_of algoritmasının all_of algoritmasının lojik değilidir demek yanlış olmaz.
STL Test Algoritmalarından none_of örneği
Yukarıdaki örnekte string türünden bir vector container elemanlarının hepsinin "Aydin" elemanına eşdeğer olup olmadığı kontrol edilmiş ve bir tane bile olmadığı için true değer olmuştur. Bir tane bile koşul sağlansaydı false değer dönecekti. ikinci kontrolde ise container elemanlarının hepsinin uzunluğu 7'den küçük olmadığı kontrolü yapılmış ve hiç bir container elemanının uzunluğu 7'den küçük olmadığı için koşul sağlandığı için true geri dönmüştür.

The Rule of Zero

06:44
"The Rule of Zero" kuralı sınıfınıza ait destructor, copy constructor, move constructor, copy assignment operator ve move assignment operator fonksiyonlarının hiçbirinin doğrudan bizim tarafımızdan tanımlanmaması anlamına gelmektedir. Peki ne zaman? bu kurala uymak gerekir. Bu noktada önce iki ayrıma dikkat etmek gerekir:

  • Yukarıda bahsi geçen özel fonksiyonlardan herhangi bir fonksiyonu tanımlamaktan kaçınabiliyorsanız, kaçının. Bu sayede hiçbirinin tanımlanmasına gerek kalmıyorsa buna kısaca "Rule of Zero" denilmektedir.
  • Yukarıda bahsi geçen özel fonksiyonlardan herhangi bir fonksiyonu tanımlıyorsanız tamamını tanımlayın, delete ediyorsanız tamamını delete edin. Buna kısaca "Rule of Five" denilmektedir.
Yukarıda iki kuraldan bizi ilgilendiren kısım olan Rule of Zero için şu denilebilir ki; tanımlamaktan kaçabiliyorsanız tanımlamayın. Peki C++ için bu idiomlar neden vardır? veya arka planda neyi ilgilendiriyor? derseniz karşımıza alt seviye dillerde önemli bir sorun olan kaynak yönetimi çıkmaktadır. C++ için Garbage Collector gibi bir yapı olmadığından dolayı kaynakların yönetimi nasıl olacak? sorusuna idiomlar ile çözüm getirilmeye çalışılmıştır. Bu noktada şunu da belirtmeliyiz ki kaynak yönetimi için C'de yer alan raw pointer'lar asla kullanılmamalıdır.  Ve bir C++ programcısı perspektifinden sistemdeki kaynak yönetimi söz konusu olduğunda, nesneleri dört biçimde kategorize edebiliriz:
  • Taşınabilen ve kopyalanabilen nesneler
  • Kopyalanabilen fakat taşınamayan nesneler
  • Taşınabilen fakat kopyalanamayan nesneler
  • Ne kopyalanabilen ne de taşınabilen nesneler
Bu nesnelerin tipine göre sınıf tanımlarında aşağıdaki fonsiyonların tanımlanması ya da delete edilmesi gerekmektedir. Bu fonksiyonlar;
  • Copy constructor
  • Copy assignment operator
  • Move constructor
  • Move assignment operator
Aşağıdaki örnekte Rule of Zero kuralına uygun olarak Car sınıfı tanımlanırken hiçbir özel fonksiyon tanımlaması yapılmamıştır. Ve kaynak yönetimi doğrudan compilerın yazdığı koda havale edilmiştir.
 

Aşağıdaki örnekte sınıfımızın move/copy constructible ya da assignable olup olmadığını incelemeye çalıştık.


Bu yazı Rule of Three/Five yazısıyla devam edilecektir.

Note:

  • default constructor:   X()
  • copy constructor:   X(const X&)
  • copy assignment:   operator=(const X&)
  • move constructor:   X(X&&)
  • move assignment:   operator=(X&&)
  • destructor:   ~X()

PIMPL Idiom (PImpl idiyomu) - 1

12:12 , ,
"Pointer based IMPlementation" veya daha yaygın/kısa ismiyle PImpl bir C++ programlama tekniğidir. Neden böyle bir kullanım yöntemi geliştirilmiştir derseniz:
  • Derleme bağımlılıkları azaltarak yapılacak değişikliklerden kullanıcı kodlarını en az etkilenmesini sağlama
  • Kaynak kodun dış dünyaya açılmasını önleme
gibi iki temel ihtiyacı karşılaması içindir. Bunun için temelde bir pointer ve bir sınıf kullanılmaktadır.

PImpl bir yönüyle compiler firewall'u gibi calışmaktadır 
Bu noktada programlamaya ilişkin iki temel kavram açıklamak gerekir:
  • Başlık dosyası (Header file, .h,.hpp, ...) : hangi sınıfları/fonksiyonları dış dünyaya açtığınızı ve nasıl kullanılacağını gösterir. Dış dünyayla paylaşılması gereken dosyadır.
  • Binary dosyalar (.lib, .dll, .a, .so, ...) : .c, .cpp, .cxx, ... dosyaların derlenmiş halleri ve başlık dosyaları ile birlikte verilecek 2. dosyadır.
.h gibi başlık dosyaları 3. kişilere verilmesi gereken ve içine yazılan her şeyi karşı tarafın açık bir şekilde görebileceği dosyalardır. Bu nedenle PImpl idiyomu bize kısaca başlık dosyasına daha az kaynak kod yazmamıza (yaptığınız implementasyonun detaylarını gizlemek ve başlık dosyalarında çok sık değişiklik yapmamaya çalışmak) olanak sağlayan idiyomdur.

cripto.h başlık dosyası
Yukarıdaki kodda görüleceği üzere sınıfın dış dünyaya açtığı iki tane fonksiyon bulunmakta ve PImpl idiyomunu gerçekleştirmek için private bölüme iki temel bildirim yapılmış.

cripto.cpp kaynak kod dosyası
Yukarıdaki kaynak kodun kullanıcı tarafından görülemeyeceğini düşünürsek yaptığımız ikinci bir sınıf kodunun hiç bir şekilde (reverse edilmediğini varsayarsak :) dışarıya sızmayacağı ve cripto.cpp dosyasında yapılacak bir değişiklik için kullanıcı tarafında bir derleme işlemine gerek olmamakta, sadece yeni derlenmiş .so benzeri dosyanın paylaşılması yeterli olacaktır.

cripto.cpp kaynak kod dosyası (Devam)
Yukarıdaki kodda dikkat çekmek istediğim nokta ise constructor tanımlaması yapılırken pImpl'ye atama yapıldığının gözden kaçmamasıdır. Atama işlemini unutmanız halinde program segmentation fault ile sonlanacaktır.

main.cpp kaynak kod dosyası
Yazdığımız yukarıdaki kodda tamamen tasarımsal olarak 2 farklı şekilde nesne tanımlaması yapılabilmekte ve buna ilişkin test amaçlı yazdığımız kod görülmektedir.

Note:

  • Yardımcı olması açısından Linux için derleme parametreleri:
    g++ -std=c++11 -O2 -Wall -c cripto.cpp
    g++ -std=c++11 -O2 -Wall main.cpp cripto.o -o pimpl
    ./pimpl

Multithreading | std::mutex - 1

Mutex (mutual exclusion)  işletim sistemlerinde olan bir senkronizasyon nesnesidir. Bir mutex'e ayni anda sadece bir thread sahip olabilmektedir. Multithreading sistemlerde deadlock 'lari ve race condition 'ları  engellemek icin kullanilan bir senkronizasyon nesnesidir.
Mutex bir thread tarafından ele alınır ve mutex’in sahipliğini alan thread'ten başka bir thread bu mutex’i ele almaya çalışınca bloke olur. Mutex’in sahibi olan thread mutex nesnesini bırakana kadar diğer thread bekleme moduna geçer. 


Yukarıdaki örneğe bakılırsa 10 tane thread 'lik havuz oluşturulmuş ve daha sonra join edilmiştir. Fakat std::vector veri yapısı burada thread'ler arasında senkronizasyon amaçlı olarak korunmadığı için aşağıda görülebileceği üzere program hatayla sonlanmıştır. 


Thread'ler eğer bir veri yapısını ortak olarak kullanmaları gerekiyorsa senkronizasyon nesnelerinden faydalanmak gerekmektedir. Bundan sonra yazının sonuna kadar senkronizasyon nesnesi olarak mutex 'leri kullanacağız.


Yukarıdaki örnekte mutex 'in içsel fonksiyonu olan lock() ile thread mutex nesnesini kilitler veya başka bir thread tarafından kilitlenmiş ise istek gönderen thread beklemeye alınır.


Yukarıdaki örnek çalıştırıldığında görebileceğiniz üzere program hatasız çalışıyor ve her thread istenen ekleme işlemini gerçekleştirdiği görülebilir. Ama thread önceliği gibi bir kısıtlama olmadığı için aynı program tekrar çalıştırıldığında ekleme sıralamasının farklı olduğu görülebilmektedir.


Yukarıdaki örnekte ise mutex nesnesinin lock/unlock işlemleri std::lock_guard ile sağlanmıştır. Bu sayede mutex'in unlock mekanizmasını unutmak diye bir hatanında önüne geçilmiş olmaktadır.


Yukarıdaki örnekte ise mutex nesnesinin lock/unlock işlemleri std::unique_lock ile gerçeklenmiştir. std::unique_lock sınıfının constructor'ının ikinci parametresine std::defer_lock parametresi geçilerek construction aşamasında mutex'in lock işleminin yapılmamasını sağlayabiliriz. Daha sonra bu işlemi unique_lock sınıfının lock() ve unlock() fonksiyonlarıyla yapabiliriz. Bu noktada eğer unlock işlemini unutursak std::unique_lock sınıfının destructor'ı bizim yerimize yapacaktır.



Multithreading | std::thread - 1

İşletim sistemleri terminolojisinde çalışmakta olan programlara process denilmektedir. Thread'ler için lightweight process'ler denilmektedir. Thread'ler process'lerin farklı çizelgelenen akışlarıdır. İşletim sistemi tüm process'lerin thread'lerini bir çizelgede tutmakta ve bunları zaman paylaşımlı olarak çalıştırılmaktadır. Bir thread biraz çalıştırılmakta sonra çalışmasına ara verilmektedir. Başka bir thread kalınan yerden çalışmaya devam ettirilmektedir. Bir process'in thread 'leri için Stack ve Register değerleri/alanları farklı; Heap, Code, Data ve Files değerleri/alanları ise ortaktır.


C++11 ile gelen std::thread ile multithreading programlama olanakları artmıştır. Aşağıda C++11 ile gelen sınıfın üye fonksiyonları ve diğer özellikler listelenmiştir.


Aşağıda basit bir thread uygulaması yapılmış ve yapılmak istenen işlemler threadler üzerinden yapılmaya çalışılmıştır. Burada join işlemini unutmamız sistemin hata vererek sonlanmasıa neden olacaktır.


Aşağıdaki thread uygulaması ise yapılacak işlemler lambdalar ve functor'lar ile thread'ler üzerinden yapılmaya çalışılmıştır.


Aşağıdaki thread uygulamasında ise fonksiyonlar ve functor'lar ile thread'ler üzerinden parametrelerin nasıl aktarılacağı anlatılmıştır.


Aşağıdaki thread uygulamasında ise thread'ler üzerinden referans yoluyla parametrelerin nasıl aktarılacağı anlatılmaya çalışılmıştır.


Yukarıdaki kodu derleyip çalıştırdığımızda ival değerinin thread tarafından değiştirilmiş olduğunu görebiliriz.


Burada dikkat edilmesi gereken nokta std::ref() fonksiyonunun kullanılmaması aşağıdaki gibi bir hatanın çıkmasına yol açmaktadır. Fonksiyon parametre olarak bir referans istiyor ve bu sağlanmazsa hatayla karşılaşılıyor.


Aşağıdaki thread örneğinde ise bir sınıfın üye fonksiyonunun thread tarafından nasıl çağrılacağına ilişkin örnek kullanım biçimi işlenmiştir.


Sınıf Şablonları (Class Templates)

08:33 ,
Sınıf şablonları da fonksiyon şablonlarında olduğu gibi benzer amaçlar için kullanılmaktadır. Generic programming denilen bu amaç tek bir kod yazıp birden fazla defa kullanılmasına olanak sağlamasıdır. 
Sınıf şablonu örneği - 1
Sınıf şablonlarına ilişkin örnek kod yazımı sırasında görebileceğimiz üzere çok sık kullandığımız container'lar aslında birer template sınıf şablonlarıymış. Örneğin: std::vector<int>, vb  
Biz de STL container'larına benzer şekilde tek bir stack template sınıf şablonu kodu üzerinden birden fazla biçimde stack türü oluşturabilmemize olanak sağlamış olduk.

Sınıf şablonu örneği - 2

Yukarıdaki örnektede benzer şekilde bir dizi şablonu oluşturulmuş ve farklı iki biçimde kullanımına olanak sağlanmıştır. Burada template yazarken int N diyerek bazı türleri sabit türde yazabilme olanağı olduğunu gözden kaçırmamak gerekir.

Fonksiyon Şablonları (Function Templates)

06:27 , ,
Template'in kelimesinin anlamı şablon ve C++'daki function template olarak kullanım şekli de fonksiyonların şablon olarak yazılmasına olanak sağlamasıdır. Bu sayede yazılan fonksiyonun farklı parametre  türlerine  uygun olarak derleyici tarafından istenen biçimlerde yazılmasına olanak sağlamasıdır. Bu sayede C'de olduğu gibi bir fonksiyonu farklı farklı tipler için ayrı ayrı yazmak yerine, tek bir  fonksiyon şablonu yazıp  bunu farklı farklı tipler için kullanım esnekliği sağlamaktadır.

template <typename T>
return-type func-name(parameter list) {
   // body of function

Template yazarken tip oluşturmak için typename veya class anahtar kelimesi kullanılabilir. İkisi de aynı anlama gelmektedir. Ama class kelimesinin kullanılması yanlış anlamlara çıkabileceğinden dolayı typename kelimesini kullanmak daha doğru olacaktır.

Function Template Örneği

C++11 ile gelen Variadic Function Template örneği ise aşağıdaki gibidir. Temelde şablon fonksiyonların parametreleri sabit ama veri tipleri değişkendir. Fakat variadic versiyonunda ise parametre sayısı da değişken biçimdedir. Bu konu şimdilik bir örnekle açıklansa da daha sonra başlı başına bir yazının konusu olacaktır.


Variadic Function Template Örneği
Fonksiyon şablonlarına parametreleri değer veya referans yoluyla geçilmesine ilişkin oluşabilecek problemleri içeren bir kod örneği aşağıda verilmiştir.


Eğer kodun içinde <int&> kısmını kaldırırsak aşağıdaki gibi bir hatayla karşılaşırız. Bunun nedeni type deduction yaparken compiler ilk parametreye bakarak T türünün int olduğunu düşünmekte fakat ikinci parametreye baktığında ise T türünün int& olduğuna karar vermekte ve hata mesajıyla karşılaşılmaktadır. Bu nedenle compiler'a T türünü doğrudan söyleyerek problemi aşabiliriz. 


Yazdığımız template için compiler'ın deduction yapmadan doğrudan kendimiz türünü bildirmek için de yukarıdaki örneğe benzer şekilde fonksiyonun yanına <int,double, ...> biçiminde belirtebiliriz.

Note:

  • Template fonksiyonumuzdaki parametreleri ve geri dönüş tipini referans yapmak daha iyi  performans sağlanmasına yardımcı olacaktır.

std::bind function adaptor (std::bind fonksiyon uyumlandırıcısı) - 1


C++11 ile standart kütüphaneye eklenen bind fonksiyon şablonu genel amaçlı bir fonksiyon (function adapter) uyumlandırıcısıdır.  Bir fonksiyonun istenildiği sekilde çağrılma kalıbına uygun bir obje oluşturarak saklamamızı sağlayan şablon sınıfıdır. Aşağıda objenin oluşturulma biçimini daha net görebiliriz.
std::bind, standart kütüphanede önceden beri var olan, kullanımı daha zahmetli ve biraz da sorunlu olan ptr_fun, mem_fun, mem_fun_ref, bind1st ve bind2nd fonksiyon uyumlandırıcılarının yerine gelmiştir. Ve bu uyumlandırıcılar standartlar tarafından deprecated kabul edilmiştir. std::bind'ın kökeni boost::bind'a dayanmaktadır. Lambda'lar olmadan önce önemli bir eksikliği gidermesi öngörülürken lambdalarla aynı zamanda standart hale gelmiş olması biraz kıyıda/köşede kalmasına sebep olmuştur. Kullanım kolaylığından dolayı daha çok lambda'lar tercih edilmektedir. std::bind bizi doğrudan function pointer karmaşasında kurtarmakta ve std::placeholder ile parametrelerini manipüle etmemize olanak sağlamaktadır. Şimdi sırayla kullanım senaryolarına ve bize sağladığı faydaları/kolaylıkları inceleyelim.

std::bind | function pointer olarak kullanımı

std::bind fonksiyon pointer tanımlama karmaşasına girmeden kolaylıkla bir fonksiyonu sarmalamamıza olanak sağlamaktadır.

Yukarıdaki örnekte isGreater fonksiyonu gönderilecek parametreleri ile beraber sarmalanmış ve fonksiyon pointer biçiminde çağrılabilmiştir.

std::bind | partial function olarak kullanımı

Parçalı fonksiyon biçiminde kullanımdan kasıt fonksiyon argumanlarından bazılarının sabitlenmesi anlamına gelmektedir.
Yukarıdaki örnekte isGreater fonksiyonunun birinci parametresi placeholders ile dış dünya ile ilişkilendirilmiş ikinci parametre ise sabit bir değere ayarlanmıştır.

placeholders değişkenleri

std::bind'a geçilen bağlanmış placeholders::x 'lar bize 'bind' edilen fonksiyona, çağrılma esnasında çeşitli parametrelerle çağırma esnekliği sağlamaktadır. Bu sayede fonksiyonun sarmalanması sırasında tüm parametrelerin girilmesine gerek kalmamaktadır. Bir placeholder değişkenin ismi, bağlanacak argüman listesinde hangi sırada kullanılmış ise, fonksiyon nesnesi ile yapılan çağrıda bu ismin işaret ettiği x numaralı parametre, sarmalanan fonksiyonun bu sıradaki parametresine argüman olarak geçileceği anlamına gelir. Daha açıklayıcı olması için:
auto f = std::bind(func, _3, _2, _1);
f(2,4,6) 
biçimindeki örneği inceleyelim:
  • placeholders::_1 ismi bind‘a geçilen bağlanacak argümanlar listesinde üçüncü sırada kullanılmış ise oluşturulan fonksiyon nesnesi ile yapılan çağrıda kullanılan birinci argüman sarmalanan fonksiyonun üçüncü parametresine aktarılır.
  • placeholders::_2 ismi bind‘a geçilen bağlanacak argümanlar listesinde ikinci sırada kullanılmış ise oluşturulan fonksiyon nesnesi ile yapılan çağrıda kullanılan ikinci argüman sarmalanan fonksiyonun ikinci parametresine aktarılır.
  • placeholders::_3 ismi bind‘a geçilen bağlanacak argümanlar listesinde birinci sırada kullanılmış ise oluşturulan fonksiyon nesnesi ile yapılan çağrıda kullanılan üçüncü argüman sarmalanan fonksiyonun birinci parametresine aktarılır.
Şimdi yukarıdaki bilgilere göre f(2,4,6) nasıl çağrılacak dersek;
tabiki func(6,4,2) şeklinde olacaktır.

std::bind | value  & reference semantics olarak kullanımı

std::bind value semantics çalıştığından dolayı dışarıdan geçilen parametreleri value type olarak  kullanır. Yukarıda örneğin dikkat çektiği nokta fonksiyon ne kadar referans yoluyla parametre alıyor olsa da std::bind bu ival1 ve ival2 değişkenlerini referans yoluyla aktarmadığını ve program çalıştırılırsa ekrana 7, 9 sayılarının yazılacağı ilişkindir. Referans yoluyla aktarım yapabilmek için yapılması gereken aşağıdaki örnekte açıklanmıştır.
std::bind'ın referans semantics çalışmasını istiyorsak std::ref'i kullanmamız ve bu sayede ival1 ve ival2 değişkenlerini referans yoluyla aktarılacağı ve program çalıştırılırsa ekrana 49, 81 sayılarının yazıldığı görülecektir.

Note:

  • place_holders nesneleri bağlanacak argümanlar listesinde birden fazla yerde de kullanılabilir:

std::bind(squareof, std::placeholder::_1, std::placeholder::_1)

decltype specifier (decltype fonksiyonu) - 1

07:25 ,
decltype, C++11 ile gelen ve çoğunlukla generic programlamada kullanılan özelliklerden biridir. Türden bağımsız (generic) olarak yazılan kodlarda, bir ifadenin türünün derleme zamanında derleyici tarafından yapılacak bir çıkarımla anlaşılması gereken durumlarda yardımımıza yetişmektedir.  Kısaca kendisine verilen ifadenin türünü döndürür. Aynı sizeof() gibi derleme zamanında işini yapar. Aşağıdaki bir değişken için tür bildirimi örneğini görebiliriz: 

Basit bir decltype ile değişken bildirim örneği
decltype(...) ve decltype((...)) farklı anlamlara gelir. Eğer int param = 3; bildiriminin ardından decltype(param) ifadesi int sonucunu verir. decltype((param ))  ifadesi ise int& sonucunu verir. Parantezlere ilişkin bu kurala dikkat edilmesi gerekir.

decltype & auto

ilk bakışta benzer işler yaptığı izlenimi veren bu iki kavram aslında birbirinden farklı biçimde çalışmaktadır. decltype eğer bir değişkene uygulanmıyorsa çıkarım yapar ve çıkarım yaparken auto'nun tür belirleme kurallarına uymaz. Bu sebeple ikisinin uyguladığı çıkarım kurallarını ayrı ayrı bilmek gerekmektedir. 

const int bir değişkeni nasıl algıladıklarına ilişkin örnek aşağıdadır:
const'luk söz konusu olduğunda decltype ile auto farklılığı örneği
Yukarıdaki farklılığa değineceğimiz ilk nokta const (top level const) olması durumunda auto için bunun bir anlamı olmasa da decltype için const'luk dikkate alınacaktır ve;
  • aval int türünden
  • dval const int türünden bildirilecektir.
(const) int& bir değişkeni nasıl algıladıklarına ilişkin örnek aşağıdadır:
referans'lık söz konusu olduğunda decltype ile auto farklılığı örneği
Yukarıdaki farklılığa değineceğimiz ilk nokta referans olması durumunda auto için bunun bir anlamı olmasa da decltype için referans'lık dikkate alınacaktır ve;
  • aval int türünden
  • dval const int& türünden bildirilecektir.
bir container elemanını nasıl algıladıklarına ilişkin örnek aşağıdadır:
container söz konusu olduğunda decltype ile auto farklılığı örneği
Yukarıdaki farklılığa değineceğimiz ilk nokta vector[0] int türünden olduğu için auto için sadece bunun bir anlamı olacak, decltype için ise vectörün operator[] geri dönüş türü (std::vector<int>::operator[](size_type) const) dikkate alınacaktır ve;
  • aval int türünden
  • dval const int& türünden bildirilecektir.
bir r-value değişkeni nasıl algıladıklarına ilişkin örnek aşağıdadır:
r-value referans'lık söz konusu olduğunda decltype ile auto farklılığı örneği

Yukarıdaki farklılığa değineceğimiz ilk nokta r-value referans olması durumunda auto için bunun bir anlamı olmasa da decltype için r-value referans'lık dikkate alınacaktır ve;
  • aval int türünden
  • dval int&& türünden bildirilecektir.

Son olarak decltype içerisine  değişken dışında ifade yazılması durumunda işlenecek kurallar aşağıdaki gibidir:
decltype(expr)
biçiminde bir kullanımda expr bir değişken değil ise, T ise bu ifadenin türü olsun;
Eğer expr bir lvalue (sol taraf değeri) ise decltype(expr) T& olarak ele alınır.
Eğer expr bir xvalue ise decltype(expr) T&& olarak ele alınır.
Eğer ifade bir prvalue (saf sağ taraf değeri) ise decltype(expr) T türü olarak ele alınır.

Generalized Constant Expressions (Genelleştirilmiş sabit ifadeler) - 1

12:53 ,
C++11'de, yazılan programların her zamankinden daha hızlı çalışmasına olanak tanıyan bir çok özellik bulunmaktadır. Bu özelliklerden biri de genelleştirilmiş sabit ifadeler (Generalized Constant Expressions), yani yazılan programların bazı parçalarının derleme zamanında hesaplamasından yararlanılmasını sağlamaktır. Sabit ifadelerin temel fikri, belirli hesaplamaları program çalıştırıldığında değil de derleme zamanına havale etmektir. Bu yöntem, görünür bir performans avantajına sahiptir: Programın her çalıştırıldığında bazı hesaplamaların tekrar tekrar hesaplanmasına gerek kalmadan bir sefer yapıp işlem maliyetini O(1) düzeyine çekmektir. Kuralı tek bir cümleyle ifade etmek gerekirse: Derleme işlemi sırasında bir şey yapılabiliyorsa, onu derleme zamanına havale et bitsin. Örneğin bir sayının sinüs veya kosinüs değerini hesaplamanız mı gerekiyor? Bunu çalışma zamanına havale etmeden derleme zamanında bunu hesaplayan bir fonksiyon oluşturarak halletmek performans artışı demektir. Son olarak genelleştirilmiş sabit ifadeleri constexpr anahtar kelimesi kullanılarak oluşturulur.
Genelleştirilmiş sabit ifadelere ait bir fonksiyon örneği
Genelleştirilmiş sabit ifadelere ait bir fonksiyon içerisinde dikkat edilmesi gereken kısıtlamalardan biri constexpr bir fonksiyon sadece constexpr fonksiyon çağrısında bulunabilir. Recursive bir çağrı kısıtlaması yoktur. Aşağıda örnek bir uygulama görülebilir:
Recursive genelleştirilmiş sabit ifadelere ait bir fonksiyon örneği
Dizi tanımlaması yapılırken bir hesap yapılması gerektiğinde klasik fonksiyon çağrısı yapılamamakta (derleme zamanında dizinin boyutunun bilinmesi gerektiğinden) ve makrolar kullanılmaktadır. C++'ın makrolarla arası iyi olmadığını da bir kenara koyarsak bu noktada makroları devre dışı bırakıp yerine genelleştirilmiş sabit ifadeleri kullanılabilir. Aşağıda buna ilişkin bir örneği inceleyebilirsiniz:
Makro yerine genelleştirilmiş sabit ifadelere ait bir fonksiyon örneği
Genelleştirilmiş sabit ifadelerine ilişkin kullanım senaryolarından biri de nesneleri compile time sırasında kullanımına ilişkindir. Aşağıda genelleştirilmiş sabit ifadeleri kavramının olmadığı bir örneği görebiliriz:
Genelleştirilmiş sabit ifadeler ile Compile Time'da nesne kullanımı örneği -1
Peki nu noktada ben bir kare objesini derleme zamanında oluşturup alanını  derleme zamanında hesaplamak istediğimde ne yapmam gerekir? Birincisi constructor constexpr olmalı ve ikincisi çağıracağım fonksiyon constexpr olmalıdır.
Genelleştirilmiş sabit ifadeler ile Compile Time'da nesne kullanımı örneği -2

Constexpr değişkenler üzerindeki kısıtlamalar

  • Constexpr gösterici değişkenlere de sabit ifadesi olma özelliğine sahip adreslerle ilk değer verilmelidir. Bu noktada şu bilinmelidir ki, yerel değişkenlerin adresleri sabit ifadesi olarak kabul edilmemekte fakat global değişkenlerin adresleri sabit ifadesi olarak kabul edilmektedir.
  • Fonksiyonların parametre değişkenleri ve bir sınıfın static olmayan veri öğeleri constexpr olarak bildirilemezler.
  • constexpr nesneye sabit ifadeleriyle ilk değer verilmesi zorunluyken const nesne için böyle bir zorunluluk yoktur. 


Constexpr fonksiyonlar üzerindeki kısıtlamalar

  • Tek return ifadesinden oluşmalıdır (birkaç istisna dışında)
  • Yalnızca diğer constexpr fonksiyonlarını return ifadesi içinde çağırabilir.
  • constexpr anahtar kelimesinin fonksiyon tanımlamasında kullanıldığında fonksiyonu otomatik olarak inline yapar (constexpr fonksiyonlar tipik olarak başlık dosyalarında tanımlanmalıdır).

Note:

  • Derleyicinin derleme zamanında değerini bilme ya da hesaplama garantisi olan ifadelere sabit ifadesi denmektedir.
  • constexpr ile tanıtılan bir değişkene bir sabit ifadesi ile ilk değer verilmeli  ve derleyici bu kontrolü yapmakla yükümlüdür. constexpr bir değişkene ilk değer verilmemesi ya da sabit ifadesi olmayan bir ifade ile ilk değer verilmesi doğrudan kural hatasıdır.
  • constexpr anahtar sözcüğü hem bir değişkenin hem de bir fonksiyonun tanımında  kullanılabilmektedir.
  • constexpr bir değişken ifadesi bir dizi tanımında boyut ifadesi olarak kullanılabilir. Bir numaralandırma (enumaration) değeri olarak kullanılabilir. Bir switch deyiminin case ifadesi olabilir. Sabit parametreli (nontype template parameter) bir şablonun açılımında arguman olarak kullanılabilir.