C++ El patrón de plantilla curiosamente recurrente (CRTP)


Ejemplo

CRTP es una poderosa alternativa estática a las funciones virtuales y la herencia tradicional que se puede usar para dar propiedades de tipos en tiempo de compilación. Funciona al tener una plantilla de clase base que toma, como uno de sus parámetros de plantilla, la clase derivada. Esto permite que se realice legalmente una static_cast de su this puntero a la clase derivada.

Por supuesto, esto también significa que una clase CRTP siempre debe usarse como la clase base de alguna otra clase. Y la clase derivada debe pasar a la clase base.

C ++ 14

Supongamos que tiene un conjunto de contenedores que admiten las funciones begin() y end() . Los requisitos de la biblioteca estándar para contenedores requieren más funcionalidad. Podemos diseñar una clase base CRTP que proporcione esa funcionalidad, basada únicamente en begin() y end() :

#include <iterator>
template <typename Sub>
class Container {
  private:
    // self() yields a reference to the derived type
    Sub& self() { return *static_cast<Sub*>(this); }
    Sub const& self() const { return *static_cast<Sub const*>(this); }

  public:
    decltype(auto) front() {
      return *self().begin();
    }

    decltype(auto) back() {
      return *std::prev(self().end());
    }

    decltype(auto) size() const {
      return std::distance(self().begin(), self().end());
    }

    decltype(auto) operator[](std::size_t i) {
      return *std::next(self().begin(), i);
    }
};

La clase anterior proporciona las funciones front() , back() , size() y operator[] para cualquier subclase que proporcione begin() y end() . Un ejemplo de subclase es una matriz simple asignada dinámicamente:

#include <memory>
// A dynamically allocated array
template <typename T>
class DynArray : public Container<DynArray<T>> {
  public:
    using Base = Container<DynArray<T>>;

    DynArray(std::size_t size)
      : size_{size},
      data_{std::make_unique<T[]>(size_)}
    { }

    T* begin() { return data_.get(); }
    const T* begin() const { return data_.get(); }
    T* end() { return data_.get() + size_; }
    const T* end() const { return data_.get() + size_; }

  private:
    std::size_t size_;
    std::unique_ptr<T[]> data_;
};

Los usuarios de la clase DynArray pueden usar las interfaces proporcionadas por la clase base CRTP de la siguiente manera:

DynArray<int> arr(10);
arr.front() = 2;
arr[2] = 5;
assert(arr.size() == 10);

Utilidad: este patrón evita particularmente las llamadas a funciones virtuales en tiempo de ejecución que ocurren para atravesar la jerarquía de herencia y simplemente se basan en conversiones estáticas:

DynArray<int> arr(10);
DynArray<int>::Base & base = arr;
base.begin(); // no virtual calls

La única Container<DynArray<int>> estática dentro de la función begin() en el Container<DynArray<int>> clase base Container<DynArray<int>> permite que el compilador optimice drásticamente el código y no se realicen búsquedas de tablas virtuales en el tiempo de ejecución.

Limitaciones: debido a que la clase base tiene una plantilla y es diferente para dos DynArray s diferentes, no es posible almacenar los punteros a sus clases base en una matriz de tipo homogéneo como se podría hacer con la herencia normal, donde la clase base no depende de la derivada tipo:

class A {};
class B: public A{};

A* a = new B;