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: AliceTemel 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.
Görev
KolaySanal 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
Countablemixin ş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 birgetCount()yöntemi sağlayın.Bir
describe()yöntemi sağlayan birDescribablemixin şablonu oluşturun. Bu yöntem, türetilmiş sınıfa erişmek ve onungetDescription()yöntemini çağırmak içinstatic_castkullanmalı, 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 deDescribable<Player>sınıflarından miras alan birPlayersınıfı oluşturun. Bir isim ve seviye saklamalı ve şu formatta bir dize döndüren birgetDescription()yöntemi sağlamalıdır:Player: [name] (Level [level])Yine her iki mixin'den miras alan bir
Enemysınıfı oluşturun. Bir tür ve sağlık saklamalı ve şu formatta bir dize döndüren birgetDescription()yöntemi sağlamalıdır:Enemy: [type] with [health] HPHer sınıf için statik sayacı başlatmayı (initialize) unutmayın.
main.cpp: Dört girdi okuyun (her biri ayrı bir satırda):- Oyuncu adı (string)
- Oyuncu seviyesi (integer)
- Düşman türü (string)
- Düşman sağlığı (integer)
Verilen değerlerle bir Player ve bir Enemy oluşturun. Ardından mixin'leri gösterin:
- Statik
getCount()yöntemini kullanarakPlayer count: [count]yazdırın Enemy count: [count]yazdırın- Oyuncu üzerinde
describe()yöntemini çağırın - Düşman üzerinde
describe()yöntemini çağırın - Adı "Guest" ve seviyesi 1 olan ikinci bir oyuncu oluşturun
- Güncellenmiş sayıyı göstermek için tekrar
Player count: [count]yazdırın - İ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;
}
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
1OOP Temelleri
Harici DosyalarC++ Build ve DerlemeBaşlık Dosyaları ve Kaynak DosyalarıAd Alanları ve KapsamC++'ta OOP'ye GirişSınıflar ve Nesneler'this' İşaretçisiMetotlar (Üye Fonksiyonlar)Öznitelikler (Veri Üyeleri)Ctor ve Dtor TemelleriÖzet - Basit Hesap Makinesi4Sınıf Özellikleri
Örnek ve Statik ÜyelerGetter ve Setter MetotlarıConst Üye FonksiyonlarMutable Anahtar KelimesiStatik Metotlar ve DeğişkenlerFriend Fonksiyonlar ve SınıflarÖzet - Banka Hesabı Yöneticisi7Kalıtım
Temel KalıtımKalıtım Erişim SeviyeleriCtor ve Dtor Çağrılma SırasıMetot Geçersiz KılmaSanal Fonksiyonlar ve VTableÇoklu KalıtımSanal KalıtımÖzet - Çalışan Hiyerarşisi10STL Genel Bakış
STL Genel Bakış ve FelsefesiSTL KonteynerleriİteratörlerSTL AlgoritmalarıFunctor'lar ve Lambda İfadeleriÖzet - Kelime Frekansı13Tasarım Kalıpları 1. Bölüm
Tasarım Kalıplarına GirişSingleton KalıbıFactory ve Abstract FactoryBuilder KalıbıObserver KalıbıStrategy Kalıbı2Bellek Yönetimi
Stack ve Heap Bellekİşaretçiler ve ReferanslarDinamik Bellek (new/delete)C++'ta Akıllı İşaretçilerC++'ta RAIIÖzet - Dinamik Dizi Yöneticisi5Kapsülleme
C++'da Erişim BelirleyicilerDerinlemesine Erişim BelirleyicilerBilgi GizlemeStruct vs Classİç İçe ve Dahili SınıflarÖzet - Öğrenci Kayıt Sistemi8Çok Biçimlilik
Derleme ve Çalışma Zamanı Çok BiçimliliğiFonksiyon Aşırı YüklemeSanal Fonksiyonlara Yeniden BakışSaf Sanal FonksiyonlarSoyut SınıflarC++'ta Arayüz TasarımıDynamic Casting ve RTTIÖzet - Şekil Hesaplayıcı11İleri Düzey OOP Kavramları
Kompozisyon ve Kalıtım KarşılaştırmasıCRTP ile Mixin YapılarıPimpl İdiyomuTür Silme (Type Erasure)Enum Sınıfları ve Güçlü TiplendirmeOOP'de İstisna YönetimiÖzel İstisna Hiyerarşileri3Yapıcılar ve Yıkıcılar
Varsayılan YapıcıParametreli YapıcıKopya YapıcıTaşıma YapıcısıYapıcı İlklendirme ListeleriTemsilci YapıcılarYıkıcılara Derinlemesine BakışÜç / Beş / Sıfır KuralıÖzet - String Sınıfı6Operatör Aşırı Yükleme
Operatör Aşırı Yüklemeye GirişAritmetik Operatör Aşırı YüklemeKarşılaştırma Operatörü Aşırı YüklemeStream OperatörleriAtama Operatörü Aşırı Yükleme[] ve () Operatör Aşırı YüklemeTip Dönüşüm OperatörleriÖzet - Matris Sınıfı9Şablonlar
Fonksiyon ŞablonlarıSınıf ŞablonlarıŞablon ÖzelleştirmeVariadic ŞablonlarSFINAE ve Type Traits TemelleriÖzet - Generic Konteyner