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;