Каков любопытно повторяющийся шаблон шаблона (CRTP)?

Не ссылаясь на книгу, может ли кто-нибудь объяснить хорошее описание CRTP примером кода?

Ответ 1

Короче говоря, CRTP - это когда класс A имеет базовый класс, который является специализацией шаблона для самого класса A. Э.Г.

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

Это любопытно повторяется, не так ли? :)

Теперь, что это дает вам? Это фактически дает шаблону X возможность быть базовым классом для его специализаций.

Например, вы можете создать общий синглтон-класс (упрощенную версию), например, так:

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;

Теперь, чтобы сделать произвольный класс A синглтоном, вы должны сделать это

class A: public Singleton<A>
{
   //Rest of functionality for class A
};

Так ты видишь? Шаблон singleton предполагает, что его специализация для любого типа X будет унаследована от singleton<X> и, таким образом, будут доступны все его (публичные, защищенные) члены, включая GetInstance! Есть и другие полезные применения CRTP. Например, если вы хотите подсчитать все экземпляры, которые в данный момент существуют для вашего класса, но хотите инкапсулировать эту логику в отдельный шаблон (идея для конкретного класса довольно проста - иметь статическую переменную, инкремент в ctors, декремент в dtors). Попробуйте сделать это как упражнение!

Еще один полезный пример для Boost (я не уверен, как они это реализовали, но CRTP тоже подойдет). Представьте, что вы хотите предоставить только оператор < для ваших классов, но автоматически оператор == для них!

Вы можете сделать это так:

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, isn't it?
    Derived const& d2 = static_cast<Derived const&>(op2); 
    return !(d1 < d2) && !(d2 < d1);//assuming derived has operator <
}

Теперь вы можете использовать его следующим образом

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

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

Теперь, вы не предоставили явно оператор == для Apple? Но у вас есть это! Вы можете написать

int main()
{
    Apple a1;
    Apple a2; 

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

Может показаться, что вы написали бы меньше, если бы вы просто написали оператор == для Apple, но представьте, что шаблон Equality будет предоставлять не только ==, но и >, >=, <= и т.д. И вы можете использовать эти определения для нескольких классов, повторно используя код!

CRTP замечательная вещь :) HTH

Ответ 2

Здесь вы можете увидеть отличный пример. Если вы используете виртуальный метод, программа будет знать, что выполняется во время выполнения. Внедряя CRTP, компилятор выбирает во время компиляции!!! Это отличная производительность!

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);
    }
};

Ответ 3

CRTP - это метод реализации полиморфизма времени компиляции. Вот очень простой пример. В приведенном ниже примере ProcessFoo() работает с интерфейсом Base класса, а Base::Foo вызывает метод производного объекта foo(), который вы нацеливаете на виртуальные методы.

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;
}

Выход:

derived foo
AnotherDerived foo

Ответ 4

Как примечание:

CRTP может использоваться для реализации статического полиморфизма (который, как динамический полиморфизм, но без таблицы указателей виртуальных функций).

#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;
}

Вывод будет:

Derived1 method
Derived2 method

Ответ 5

Это не прямой ответ, а пример того, как CRTP может быть полезен.


Хорошим конкретным примером CRTP является std::enable_shared_from_this из С++ 11:

[util.smartptr.enab]/1

Класс T может наследовать от enable_­shared_­from_­this<T>, чтобы наследовать функции-члены shared_­from_­this, которые получают экземпляр shared_­ptr, указывающий на *this.

То есть наследование от std::enable_shared_from_this позволяет получить общий (или слабый) указатель на ваш экземпляр без доступа к нему (например, из функции-члена, где вы знаете только о *this).

Это полезно, когда вам нужно дать std::shared_ptr, но у вас есть только доступ к *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));
    }
};

Причина, по которой вы не можете просто передать this непосредственно вместо shared_from_this(), заключается в том, что она нарушит механизм владения:

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);