C++ Le modèle de modèle curieusement récurrent (CRTP)


Exemple

CRTP est une alternative puissante et statique aux fonctions virtuelles et à l'héritage traditionnel qui peut être utilisée pour donner des propriétés de type à la compilation. Cela fonctionne en ayant un modèle de classe de base qui prend, comme l'un de ses paramètres de modèle, la classe dérivée. Cela lui permet d'effectuer légalement un static_cast de this pointeur vers la classe dérivée.

Bien sûr, cela signifie également qu'une classe CRTP doit toujours être utilisée comme classe de base d'une autre classe. Et la classe dérivée doit se transmettre à la classe de base.

C ++ 14

Disons que vous avez un ensemble de conteneurs qui supportent tous les fonctions begin() et end() . Les exigences de la bibliothèque standard pour les conteneurs nécessitent davantage de fonctionnalités. Nous pouvons concevoir une classe de base CRTP qui fournit cette fonctionnalité, basée uniquement sur begin() et 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 classe ci-dessus fournit les fonctions front() , back() , size() et operator[] pour toute sous-classe fournissant begin() et end() . Un exemple de sous-classe est un tableau simple alloué dynamiquement:

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

Les utilisateurs de la classe DynArray peuvent utiliser les interfaces fournies par la classe de base CRTP facilement comme suit:

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

Utilité: ce modèle évite en particulier les appels de fonction virtuels à l'exécution qui se produisent dans la hiérarchie d'héritage et repose simplement sur des conversions statiques:

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

La seule distribution statique à l'intérieur de la fonction begin() dans la classe de base Container<DynArray<int>> permet au compilateur d'optimiser considérablement le code et aucune consultation de table virtuelle ne se produit à l'exécution.

Limitations: Comme la classe de base est basée sur des modèles et différente pour deux DynArray différents, il est impossible de stocker des pointeurs dans leurs classes de base dans un tableau homogène, comme on peut généralement le faire avec l'héritage normal. type:

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

A* a = new B;