C++ Il modello di modello Curiosamente ricorrente (CRTP)


Esempio

CRTP è un'alternativa potente e statica alle funzioni virtuali e all'ereditarietà tradizionale che può essere utilizzata per fornire proprietà dei tipi in fase di compilazione. Funziona avendo un modello di classe base che prende, come uno dei suoi parametri template, la classe derivata. Ciò le permette di svolgere legalmente uno static_cast della sua this puntatore alla classe derivata.

Ovviamente, ciò significa anche che una classe CRTP deve sempre essere utilizzata come classe base di un'altra classe. E la classe derivata deve passare alla classe base.

C ++ 14

Supponiamo che tu abbia un set di contenitori che supportano tutte le funzioni begin() e end() . I requisiti della libreria standard per i contenitori richiedono più funzionalità. Possiamo progettare una classe base CRTP che fornisce tale funzionalità, basata esclusivamente su begin() e 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 precedente fornisce le funzioni front() , back() , size() e operator[] per qualsiasi sottoclasse che fornisce begin() e end() . Un esempio di sottoclasse è un semplice array allocato dinamicamente:

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

Gli utenti della classe DynArray possono utilizzare facilmente le interfacce fornite dalla classe base CRTP come segue:

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

Utilità: questo modello evita in particolare le chiamate alle funzioni virtuali in fase di esecuzione che si verificano attraversano la gerarchia dell'ereditarietà e si basano semplicemente su valori statici:

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

L'unico cast statico all'interno della funzione begin() nella classe base Container<DynArray<int>> consente al compilatore di ottimizzare drasticamente il codice e nessuna ricerca di tabelle virtuali avviene al momento dell'esecuzione.

Limitazioni: Poiché la classe base è basata su modelli e diversa per due diversi DynArray , non è possibile archiviare i puntatori alle loro classi base in un array omogeneo di tipo come generalmente si potrebbe fare con l'ereditarietà normale in cui la classe base non dipende dal derivato genere:

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

A* a = new B;