9 Temmuz 2020 Perşembe

Curiously Recurring Template Pattern - CRTP (Static Polymorphism)

Giriş
Curiously recurring template pattern kullanımının iki tane ana konusu var.

1. Polymorphism . Örneğin Template Method Pattern için kullanılabilir.
2. Ata sınıfta kalıtan sınıf tipine olan ihtiyaç. Örneğin Binary Tree, Fluent Methods gibi kullanım çeşitlerinde gerek olabilir.

Data Structures kullanımında ata sınıf T tipinden bir başka child veya parent nesneye erişim ister. Örneğin Binary Tree, Linked List gibi veri yapılarında bu durum vardır.

Fluent Methods kullanımında ata sınıf metodu T tipi dönecek şekilde yazılır. Böylece kalıtan sınıf otomatik olarak kendini döner. Cast etmeye gerek kalmaz.


Polymorphisim Konusu
CRTP'yi anlamak için polymorphism çeşitlerini anlamak gerekir. İki çeşit polyporphism var.

1. Dynamic Polymorphism
Virtual metodlar kullanılarak yapılır. Şöyle yaparız.
struct A1 {
  virtual ~A1(){}
  virtual void method() {}
};
struct B1 : A1 {
  virtual ~B1(){}
  virtual void method() override {}
};
int main () {
  B1 b;
  A1 *a = &b;
  a->method(); // calls B1::method() but this is not known until runtime.
  return 0;
}
2. Static Polymorphism
Virtual metodlar kullanılmadan üst sınıf tipine cast edilerek yapılır.

Örnek
Şöyle yaparız.
template < class T > struct A2 {
  void method() {
    T *derived_this = static_cast<T*>(this);
    derived_this->method();
  }
};
struct B2 : A2 < B2 > {
  void method() {}
};
int main () {
  B2 b;
  A2<B2> *a = &b; // typically seen as a templated function argument
  a->method(); // calls B2::method() statically, known at compile-time
  return 0;
}
CRTP Nedir?
CRTP static polymorphism içindir. Bu yüzden CRTP içinde downcasting hep static_cast ile yapılır, dynamic_cast kullanılmaz. Kalıtan sınıfın impl() metodunun virtual olmasına gerek yoktur.

Örnek
İskelet olarak şöyle bir şeye benzer.
template <typename TDerived>
class Base {};

class Derived : public Base<Derived> {};
Şöyle kullanırız
Base<Derived1> *d1 = new Derived1();
d1->method();
CRTP mantıken sanki derlenmemesi gerekiyor gibi görünüyor Sorun çıkmaması declaration ve definition farkından kaynaklanıyor.

Örnek
Elimizde şöyle bir kod olsun.
template <class derived>
class Base {

public:
  void get_value()
  {
    double value = static_cast<derived *> (this) -> myvalue_;
    std::cout<< "This is the derived value: " << value << std::endl;
  }

};
Kalıtan sınıf şöyledir.
class derived:public Base<derived>{

public:

  double myvalue_;
  ...  
};
Kullanmak için şöyle yaparız.
derived myclass = ...
myclass.get_value();
Örnek - Hem Üye Metod Hem de Static Üye Metod
Şöyle yaparız. Burada CRTP'nin static üye metod için de kullanıldığı görülebilir.
template <class T> 
struct Base
{
  void interface()
  {
    // ...
    static_cast<T*>(this)->implementation();
    // ...
  }

  static void static_func()
  {
    // ...
    T::static_sub_func();
    // ...
  }
};

struct Derived : Base<Derived>
{
  void implementation();
  static void static_sub_func();
};
Açıklaması şöyle. Kalıtan sınıfın static metod çağrısı için static_cast yapmaya gerek yok!.
The common base class template interface provides definitions that delegates calls to the derived class implementations, realized (for non-static member functions) by downcasting the this pointer of the base class to a pointer type of the respective derived class (for the particular specialization of the base class template) followed by dispatch to a derived class function.
Örnek
CRTP'nin arayüz -> kalıtan nesne -> kalıtan nesne + crtp şeklinde kullanıldığını da gördüm. Tam olarak ne faydası var anlamadım. Bir kütüphanede şöyle kod olsun
class I{ //interface
    public: virtual void foo()=0;
};

template<class Derived>
class Router : public I{ 
  public: virtual void  foo()final{   
    static_cast<Derived*>(this)->foo(); 
  }
};
Kendimiz şöyle yaparız. foo metodu çağrılınca Router nesnesin sadece bir tane vtable çağrısı olur. Kendi kodumuz çağrılınca vtable kullanılmaz.
class User : public Router<User>{
    public: void foo(){ std::cout<<"hi"<<std::endl; }
};

User u;
u.foo();   //<-- no v-table cost
Örnek
CRTP'de ata sınıfın yaratılmasını engellemek iyi bir fikir olabilir. Şöyle yaparız.
template< typename T >
struct CRTP
{
    void do_it( )
    {
        static_cast< T& >( *this ).execute( );
    }
    friend T;
private:
    CRTP() {};
};
Şöyle yaparız.
struct A : CRTP< A >
{
    void execute( )
    {
        cout << "A" << endl;
    }
};

struct B : CRTP< B >
{
    void execute( )
    {
        cout << "B" << endl;

    }
};

struct C : CRTP< C > 
{
    void execute( )
    {
        cout << "C" << endl;
    }
};

int main( )
{
    A a;
    a.do_it( );
    B b;
    b.do_it( );
    C c;
    c.do_it( );
    return 0;
}
Çıktı olarak şunu alırız.
A
B
C
Örnek
Bu örnekte ne ortak metod ne de ortak alan var. Sadece CRTP'ye örnek olsun diye aldım. Şöyle yaparız.
class A {
public:
  int a = 1;
  A() {}
};

class B {
public:
  float b = 2.0f;
  B() {}
};

class C {
public:
  char c = 'c';
  C() {}
};

template<class T>
class D : public T {
public:
    D() : T() {}
};
Şöyle yaparız.
D<A> dA;
D<B> dB;
D<C> dC;

std::cout << "D<A>::a = " << dA.a << "\n";
std::cout << "D<B>::b = " << dB.b << "\n";
std::cout << "D<C>::c = " << dC.c << "\n";


Hiç yorum yok:

Yorum Gönder