Что такое необычно повторяющийся шаблон (CRTP)? - PullRequest
162 голосов
/ 13 ноября 2010

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

Ответы [ 5 ]

254 голосов
/ 13 ноября 2010

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

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;

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

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

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

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

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

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

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

int main()
{
    Apple a1;
    Apple a2; 

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

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

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

41 голосов
/ 03 ноября 2014

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

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
5 голосов
/ 11 октября 2013

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

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
4 голосов
/ 27 ноября 2017

Это не прямой ответ, а пример того, как 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);
...