Menu
Coddy logo textTech

CRTP ile Mixin Yapıları

Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 77 / 104.

Curiously Recurring Template Pattern (CRTP), bir sınıfın kendisini şablon argümanı olarak geçirerek bir şablon taban sınıfından miras aldığı bir tekniktir. Bu, derleme zamanı polimorfizmine olanak tanır ve taban sınıfların sanal fonksiyonlar kullanmadan türetilmiş sınıf üyelerine erişmesini sağlar.

Temel CRTP yapısı şu şekildedir:

template <typename Derived>
class Base {
public:
    void interface() {
        static_cast<Derived*>(this)->implementation();
    }
};

class MyClass : public Base<MyClass> {
public:
    void implementation() {
        std::cout << "MyClass implementation\n";
    }
};

CRTP, sınıflara "karıştırılabilen" (mixed into) yeniden kullanılabilir işlevsellikler olan mixin'ler oluşturmak için özellikle güçlüdür. Geleneksel kalıtımın aksine, mixin'ler derin hiyerarşiler oluşturmadan yetenekler ekler:

template <typename Derived>
class Printable {
public:
    void print() const {
        const Derived& self = static_cast<const Derived&>(*this);
        std::cout << self.toString() << "\n";
    }
};

class Person : public Printable<Person> {
    std::string name;
public:
    Person(const std::string& n) : name(n) {}
    std::string toString() const { return "Person: " + name; }
};

// Kullanım:
Person p("Alice");
p.print();  // Çıktı: Person: Alice

Temel avantaj, tüm metot çağrılarının derleme zamanında çözümlenmesi ve böylece sanal fonksiyon ek yükünün ortadan kaldırılmasıdır. Türler derleme zamanında bilindiğinde, işlevsellik oluşturmak için birden fazla CRTP mixin'ini birleştirebilirsiniz; bu da onu çalışma zamanı polimorfizmine esnek bir alternatif haline getirir.

challenge icon

Görev

Kolay

Sanal fonksiyon ek yükü (overhead) olmadan farklı sınıflara yeniden kullanılabilir işlevsellik eklemek için CRTP mixin'lerini kullanan bir günlükleme (logging) sistemi oluşturalım. Herhangi bir sınıfa "karıştırılabilecek" (mixed into) iki mixin oluşturacaksınız: biri örnekleri (instances) saymak için, diğeri ise dize temsilleri oluşturmak için.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • Mixins.h: Yeniden kullanılabilir işlevsellik sağlayan iki CRTP mixin şablonu tanımlayın.

    Türetilmiş bir sınıfın kaç örneğinin mevcut olduğunu izleyen bir Countable mixin şablonu oluşturun. Yapıcıda (constructor) artan ve yıkıcıda (destructor) azalan statik bir sayaca sahip olmalıdır. Mevcut sayıyı döndüren statik bir getCount() yöntemi sağlayın.

    Bir describe() yöntemi sağlayan bir Describable mixin şablonu oluşturun. Bu yöntem, türetilmiş sınıfa erişmek ve onun getDescription() yöntemini çağırmak için static_cast kullanmalı, ardından sonucu ve bir yeni satır karakterini yazdırmalıdır.

    CRTP mixin'lerinin, türetilmiş sınıfın üyelerine derleme zamanında erişmek için static_cast<Derived*>(this) kullandığını unutmayın.

  • Entities.h: Her iki mixin'den de miras alan iki varlık (entity) sınıfı tanımlayın.

    Hem Countable<Player> hem de Describable<Player> sınıflarından miras alan bir Player sınıfı oluşturun. Bir isim ve seviye saklamalı ve şu formatta bir dize döndüren bir getDescription() yöntemi sağlamalıdır: Player: [name] (Level [level])

    Yine her iki mixin'den miras alan bir Enemy sınıfı oluşturun. Bir tür ve sağlık saklamalı ve şu formatta bir dize döndüren bir getDescription() yöntemi sağlamalıdır: Enemy: [type] with [health] HP

    Her sınıf için statik sayacı başlatmayı (initialize) unutmayın.

  • main.cpp: Dört girdi okuyun (her biri ayrı bir satırda):
    1. Oyuncu adı (string)
    2. Oyuncu seviyesi (integer)
    3. Düşman türü (string)
    4. Düşman sağlığı (integer)

    Verilen değerlerle bir Player ve bir Enemy oluşturun. Ardından mixin'leri gösterin:

    1. Statik getCount() yöntemini kullanarak Player count: [count] yazdırın
    2. Enemy count: [count] yazdırın
    3. Oyuncu üzerinde describe() yöntemini çağırın
    4. Düşman üzerinde describe() yöntemini çağırın
    5. Adı "Guest" ve seviyesi 1 olan ikinci bir oyuncu oluşturun
    6. Güncellenmiş sayıyı göstermek için tekrar Player count: [count] yazdırın
    7. İkinci oyuncu üzerinde describe() yöntemini çağırın

Örneğin, Hero, 10, Dragon ve 500 girdileriyle:

Player count: 1
Enemy count: 1
Player: Hero (Level 10)
Enemy: Dragon with 500 HP
Player count: 2
Player: Guest (Level 1)

Bu zorluk, CRTP mixin'lerinin sanal fonksiyonlar kullanmadan ilgisiz sınıflara nasıl işlevsellik (sayma ve tanımlama) eklediğini göstermektedir. Hem Player hem de Enemy, aynı mixin şablonlarından miras alarak aynı yetenekleri kazanır, ancak şablon farklı türlerle örneklendiği için her biri kendi ayrı örnek sayacını korur.

Kopya kağıdı

Curiously Recurring Template Pattern (CRTP), bir sınıfın kendisini şablon argümanı olarak geçirerek bir şablon taban sınıfından miras almasıyla derleme zamanı polimorfizmini (compile-time polymorphism) mümkün kılar:

template <typename Derived>
class Base {
public:
    void interface() {
        static_cast<Derived*>(this)->implementation();
    }
};

class MyClass : public Base<MyClass> {
public:
    void implementation() {
        std::cout << "MyClass implementation\n";
    }
};

CRTP Mixin'leri, derin kalıtım hiyerarşileri olmadan yeniden kullanılabilir işlevsellik sağlar:

template <typename Derived>
class Printable {
public:
    void print() const {
        const Derived& self = static_cast<const Derived&>(*this);
        std::cout << self.toString() << "\n";
    }
};

class Person : public Printable<Person> {
    std::string name;
public:
    Person(const std::string& n) : name(n) {}
    std::string toString() const { return "Person: " + name; }
};

Temel avantajlar:

  • Tüm metot çağrıları derleme zamanında çözümlenir
  • Sanal fonksiyon (virtual function) ek yükü yoktur
  • İşlevsellik oluşturmak için birden fazla mixin birleştirilebilir
  • Her şablon örneklemesi ayrı bir durum korur (örneğin, türetilmiş tür başına ayrı statik sayaçlar)

Kendin dene

#include <iostream>
#include <string>
#include "Entities.h"

using namespace std;

int main() {
    // Girdileri oku
    string playerName;
    int playerLevel;
    string enemyType;
    int enemyHealth;
    
    cin >> playerName;
    cin >> playerLevel;
    cin >> enemyType;
    cin >> enemyHealth;
    
    // TODO: Verilen isim ve seviye ile bir Player oluşturun
    
    // TODO: Verilen tür ve sağlık ile bir Enemy oluşturun
    
    // TODO: Player::getCount() kullanarak "Player count: [count]" yazdırın
    
    // TODO: Enemy::getCount() kullanarak "Enemy count: [count]" yazdırın
    
    // TODO: Oyuncu üzerinde describe() metodunu çağırın
    
    // TODO: Düşman üzerinde describe() metodunu çağırın
    
    // TODO: "Guest" isminde ve 1 seviyesinde ikinci bir oyuncu oluşturun
    
    // TODO: "Player count: [count]" ifadesini tekrar yazdırın
    
    // TODO: İkinci oyuncu üzerinde describe() metodunu çağırın
    
    return 0;
}
quiz iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Nesne Yönelimli Programlama bölümündeki tüm dersler