Co to jest curiously recurring template pattern (CRTP)?

Bez odwoĹ 'ania do ksiÄ ... Ĺźki, czy ktoĹ" moĹźe podaÄ ‡ dobre wyjaĹ "nienie CRTP przykĹ' adem kodu?

Author: TankorSmash, 2010-11-13

5 answers

W skrócie, CRTP jest wtedy, gdy Klasa A ma klasę bazową, która jest specjalnością szablonu dla samej klasy A. Np.

template <class T> 
class X{...};
class A : public X<A> {...};

Tojest dziwnie powtarzające się, prawda? :)

Co ci to daje? Dzięki temu szablon X może być klasą bazową dla jego specjalizacji.

Na przykład, możesz utworzyć ogólną klasę singleton (uproszczoną wersję), taką jak ta

template <class ActualClass> 
class Singleton
{
   public:
     static ActualClass& GetInstance()
     {
       if(p == nullptr)
         p = new ActualClass;
       return *p; 
     }

   protected:
     static ActualClass* p;
   private:
     Singleton(){}
     Singleton(Singleton const &);
     Singleton& operator = (Singleton const &); 
};
template <class T>
T* Singleton<T>::p = nullptr;

Teraz, w celu dowolnego klasy A a singleton ty should do this

class A: public Singleton<A>
{
   //Rest of functionality for class A
};
Widzisz? Szablon Singletona zakłada, że jego specjalizacja dla dowolnego typu X zostanie odziedziczona po singleton<X> i tym samym będzie miał wszystkie jego (publiczne, chronione) członkowie dostępne, w tym GetInstance! Istnieją inne przydatne zastosowania CRTP. Na przykład, jeśli chcesz policzyć wszystkie instancje, które obecnie istnieją dla twojej klasy, ale chcesz zamknąć tę logikę w osobnym szablonie (pomysł na konkretną klasę jest dość prosty - mieć zmienną statyczną, increment in ctors, decrement in dtors). Spróbuj to zrobić jako ćwiczenie!

Kolejny przydatny przykład, dla boost(nie jestem pewien, jak go zaimplementowali, ale CRTP też to zrobi). Wyobraź sobie, że chcesz zapewnić tylko operator

Możesz to zrobić tak:

template<class Derived>
class Equality
{
};

template <class Derived>
bool operator == (Equality<Derived> const& op1, Equality<Derived> const & op2)
{
    Derived const& d1 = static_cast<Derived const&>(op1);//you assume this works     
    //because you know that the dynamic type will actually be your template parameter.
    //wonderful, isnit it?
    Derived const& d2 = static_cast<Derived const&>(op2); 
    return !(d1 < d2) && !(d2 < d1);//assuming derived has operator <
}

Teraz możesz go używać w ten sposób

struct Apple:public Equality<Apple> 
{
    int size;
};

bool operator < (Apple const & a1, Apple const& a2)
{
    return a1.size < a2.size;
}

Nie podałeś jawnie operatora = = dla apple? Ale ty to masz! Możesz napisać

int main()
{
    Apple a1;
    Apple a2; 

    a1.size = 10;
    a2.size = 10;
    if(a1 == a2) //the compiler won't complain! 
    {
    }
}

To mogłoby się wydawać, że piszesz mniej, gdybyś napisał operator = = Dla Apple, ale wyobraź sobie, że szablon równości zapewni nie tylko==, ale >,>=, wielu klas , używając kodu ponownie!

CRTP to cudowna rzecz:) HTH

 229
Author: Armen Tsirunyan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-14 09:10:35

Tutaj możesz zobaczyć świetny przykład. Jeśli używasz metody wirtualnej, program będzie wiedział, co wykonać w trybie runtime. Implementacja CRTP to kompilator, który decyduje w czasie kompilacji!!! To jest świetne przedstawienie!

template <class T>
class Writer
{
  public:
    Writer()  { }
    ~Writer()  { }

    void write(const char* str) const
    {
      static_cast<const T*>(this)->writeImpl(str); //here the magic is!!!
    }
};


class FileWriter : public Writer<FileWriter>
{
  public:
    FileWriter(FILE* aFile) { mFile = aFile; }
    ~FileWriter() { fclose(mFile); }

    //here comes the implementation of the write method on the subclass
    void writeImpl(const char* str) const
    {
       fprintf(mFile, "%s\n", str);
    }

  private:
    FILE* mFile;
};


class ConsoleWriter : public Writer<ConsoleWriter>
{
  public:
    ConsoleWriter() { }
    ~ConsoleWriter() { }

    void writeImpl(const char* str) const
    {
      printf("%s\n", str);
    }
};
 35
Author: GutiMac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-26 21:06:38

CRTP jest techniką implementacji polimorfizmu w czasie kompilacji. Oto bardzo prosty przykład. W poniższym przykładzie ProcessFoo() pracuje z interfejsem klasy Base i Base::Foo wywołuje metodę foo() obiektu pochodnego, co jest tym, co chcesz zrobić z metodami wirtualnymi.

Http://coliru.stacked-crooked.com/a/2d27f1e09d567d0e

template <typename T>
struct Base {
  void foo() {
    (static_cast<T*>(this))->foo();
  }
};

struct Derived : public Base<Derived> {
  void foo() {
    cout << "derived foo" << endl;
  }
};

struct AnotherDerived : public Base<AnotherDerived> {
  void foo() {
    cout << "AnotherDerived foo" << endl;
  }
};

template<typename T>
void ProcessFoo(Base<T>* b) {
  b->foo();
}


int main()
{
    Derived d1;
    AnotherDerived d2;
    ProcessFoo(&d1);
    ProcessFoo(&d2);
    return 0;
}

Wyjście:

derived foo
AnotherDerived foo
 7
Author: blueskin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-09 19:14:07

Tak jak uwaga:

CRTP może być używany do implementacji polimorfizmu statycznego (który podobnie jak polimorfizm dynamiczny, ale bez tabeli wskaźników funkcji wirtualnych).

#pragma once
#include <iostream>
template <typename T>
class Base
{
    public:
        void method() {
            static_cast<T*>(this)->method();
        }
};

class Derived1 : public Base<Derived1>
{
    public:
        void method() {
            std::cout << "Derived1 method" << std::endl;
        }
};


class Derived2 : public Base<Derived2>
{
    public:
        void method() {
            std::cout << "Derived2 method" << std::endl;
        }
};


#include "crtp.h"
int main()
{
    Derived1 d1;
    Derived2 d2;
    d1.method();
    d2.method();
    return 0;
}

Wyjście będzie:

Derived1 method
Derived2 method
 6
Author: Jichao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-11 06:13:56

To nie jest bezpośrednia odpowiedź, ale raczej przykład, w jaki sposób CRTP może być przydatny.


Dobrym konkretnym przykładem CRTP jest std::enable_shared_from_this z C++11:

[util.smartptr.enab]/1

Klasa T może dziedziczyć z enable_­shared_­from_­this<T>, Aby dziedziczyć shared_­from_­this funkcje Członkowskie, które otrzymują instancję shared_­ptr wskazującą na *this.

To znaczy, dziedziczenie z std::enable_shared_from_this umożliwia uzyskanie współdzielonego (lub słabego) wskaźnika do twojego instancja bez dostępu do niej (np. z funkcji member, gdzie wiadomo tylko o *this).

Jest to przydatne, gdy trzeba dać std::shared_ptr, ale masz dostęp tylko do *this:

struct Node;

void process_node(const std::shared_ptr<Node> &);

struct Node : std::enable_shared_from_this<Node> // CRTP
{
    std::weak_ptr<Node> parent;
    std::vector<std::shared_ptr<Node>> children;

    void add_child(std::shared_ptr<Node> child)
    {
        process_node(shared_from_this()); // Shouldn't pass `this` directly.
        child->parent = weak_from_this(); // Ditto.
        children.push_back(std::move(child));
    }
};

Powodem, dla którego nie można po prostu przejść this bezpośrednio zamiast shared_from_this(), jest to, że złamałoby to mechanizm własności:

struct S
{
    std::shared_ptr<S> get_shared() const { return std::shared_ptr<S>(this); }
};

// Both shared_ptr think they're the only owner of S.
// This invokes UB (double-free).
std::shared_ptr<S> s1 = std::make_shared<S>();
std::shared_ptr<S> s2 = s1->get_shared();
assert(s2.use_count() == 1);
 4
Author: Mário Feroldi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-02 16:37:50