¿Qué es el patrón de plantilla curiosamente recurrente (CRTP)?


Sin referirse a un libro, ¿puede alguien por favor proporcionar una buena explicación para CRTP con un ejemplo de código?

Author: TankorSmash, 2010-11-13

5 answers

En resumen, CRTP es cuando una clase A tiene una clase base que es una especialización de plantilla para la propia clase A. Por ejemplo,

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

Es curiosamente recurrente, ¿no es así? :)

Ahora, ¿qué te da esto? Esto realmente le da a la plantilla X la capacidad de ser una clase base para sus especializaciones.

Por ejemplo, podría hacer una clase singleton genérica (versión simplificada) como esta

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;

Ahora, con el fin de hacer una clase arbitraria A un singleton que debe hacer esto

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

Entonces, ¿ves? La plantilla singleton asume que su especialización para cualquier tipo X será heredada de singleton<X> y por lo tanto tendrá todos sus miembros(públicos, protegidos) accesibles, incluyendo el GetInstance! Hay otros usos útiles de CRTP. Por ejemplo, si desea contar todas las instancias que existen actualmente para su clase, pero desea encapsular esta lógica en una plantilla separada (la idea para una clase concreta es bastante simple: tenga una variable estática, increase in ctors, decremento en dtors). Trate de hacerlo como un ejercicio!

Otro ejemplo útil, para boost(no estoy seguro de cómo lo han implementado, pero CRTP también lo hará). Imagine que desea proporcionar solo operador

Podrías hacerlo así:

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

Ahora puedes usarlo así

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

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

Ahora, usted no ha proporcionado explícitamente operator == para apple? Pero lo tienen! Puedes escribir

int main()
{
    Apple a1;
    Apple a2; 

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

Esto podría parecer que escribirías menos si escribieras operator = = para Apple, pero imagina que la plantilla de Igualdad proporcionaría no solo = =sino>,>=, múltiples clases, reutilizando el código!

CRTP es una cosa maravillosa:) 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-14 09:10:35

Aquí puedes ver un gran ejemplo. Si utiliza el método virtual, el programa sabrá qué ejecutar en tiempo de ejecución. Implementando CRTP el compilador es el que decide en tiempo de compilación!!! Esta es una gran actuación!

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-26 21:06:38

CRTP es una técnica para implementar polimorfismo en tiempo de compilación. Aquí hay un ejemplo muy simple. En el siguiente ejemplo, ProcessFoo() está trabajando con la interfaz de clase Base y Base::Foo invoca el método foo() del objeto derivado, que es lo que pretende hacer con los métodos virtuales.

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

Salida:

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-09 19:14:07

Como nota:

CRTP podría usarse para implementar polimorfismo estático (que como polimorfismo dinámico pero sin tabla de puntero de función virtual).

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

La salida sería:

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-11 06:13:56

Esta no es una respuesta directa, sino más bien un ejemplo de cómo CRTP puede ser útil.


Un buen ejemplo concreto de CRTP es std::enable_shared_from_this de C++11:

[util.smartptr.enab]/1

Una clase T puede heredar de enable_­shared_­from_­this<T> para heredar las funciones miembro shared_­from_­this que obtienen una instancia shared_­ptr que apunta a *this.

Es decir, heredar de std::enable_shared_from_this hace posible obtener un puntero compartido (o débil) a su instancia sin acceso a ella (por ejemplo, desde una función miembro donde solo conoce *this).

Es útil cuando necesitas dar un std::shared_ptr pero solo tienes acceso a *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));
    }
};

La razón por la que no puedes simplemente pasar this directamente en lugar de shared_from_this() es que rompería el mecanismo de propiedad:

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-02 16:37:50