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


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

5 143

5 ответов:

короче говоря, 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 тоже будет делать). Представьте, что вы хотите предоставить только operator

вы могли бы сделать это вот так:

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

теперь вы можете использовать его так

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! 
    {
    }
}

может показаться, что вы написали бы меньше, если бы вы просто написали operator == для Apple, но представьте, что шаблон равенства обеспечит не только == но>,>=, несколько классов, повторное использование кода!

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

здесь вы можете увидеть отличный пример. Если вы используете виртуальный метод, программа будет знать, что выполняется во время выполнения. Реализация 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);
    }
};

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

Так же, как Примечание:

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

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


хороший конкретный пример CRTP и std::enable_shared_from_this из C++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);