Поиск…


Вступление

Шаблон, в котором класс наследует шаблон шаблона сам по себе как один из его параметров шаблона. CRTP обычно используется для обеспечения статического полиморфизма в C ++.

Любопытно повторяющийся шаблон шаблона (CRTP)

CRTP - это мощная статическая альтернатива виртуальным функциям и традиционному наследованию, которые могут использоваться для предоставления свойств типов во время компиляции. Он работает, имея шаблон базового класса, который принимает в качестве одного из своих параметров шаблона производный класс. Это позволяет ему юридически выполнить static_cast его this указателя на производный класс.

Разумеется, это также означает, что класс CRTP всегда должен использоваться как базовый класс другого класса. И производный класс должен перейти к базовому классу.

C ++ 14

Допустим, у вас есть набор контейнеров, которые поддерживают функции begin() и end() . Требования к стандартной библиотеке для контейнеров требуют большей функциональности. Мы можем создать базовый класс CRTP, который обеспечивает эту функциональность, основанную исключительно на begin() и 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);
    }
};

Вышеупомянутый класс предоставляет функции front() , back() , size() и operator[] для любого подкласса, который предоставляет begin() и end() . Примером подкласса является простой динамически распределенный массив:

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

Пользователи класса DynArray могут легко использовать интерфейсы, предоставляемые базовым классом CRTP, следующим образом:

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

Полезность. Этот шаблон, в частности, избегает вызовов виртуальных функций во время выполнения, которые происходят, чтобы пересечь иерархию наследования и просто полагается на статические приведения:

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

Единственный статический бросок внутри функции begin() в базовом классе Container<DynArray<int>> позволяет компилятору радикально оптимизировать код, и при просмотре виртуальной таблицы не происходит во время выполнения.

Ограничения: поскольку базовый класс шаблонизирован и отличается для двух разных DynArray s, невозможно сохранить указатели на их базовые классы в однородном по типу массиве, как обычно можно было бы сделать с обычным наследованием, когда базовый класс не зависит от производного тип:

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

A* a = new B;

CRTP, чтобы избежать дублирования кода

Пример в шаблоне посетителей представляет собой убедительный прецедент для CRTP:

struct IShape
{
    virtual ~IShape() = default;

    virtual void accept(IShapeVisitor&) const = 0;
};

struct Circle : IShape
{
    // ...        
    // Each shape has to implement this method the same way
    void accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
    // ...
};

struct Square : IShape
{
    // ...    
    // Each shape has to implement this method the same way
    void accept(IShapeVisitor& visitor) const override { visitor.visit(*this); }
    // ...
};

Каждому дочернему типу IShape необходимо реализовать ту же функцию одинаково. Это много лишнего набора текста. Вместо этого мы можем ввести новый тип в иерархию, который делает это для нас:

template <class Derived>
struct IShapeAcceptor : IShape {
    void accept(IShapeVisitor& visitor) const override {
        // visit with our exact type
        visitor.visit(*static_cast<Derived const*>(this));
    }
};

И теперь каждая форма просто должна унаследовать от акцептора:

struct Circle : IShapeAcceptor<Circle>
{
    Circle(const Point& center, double radius) : center(center), radius(radius) {}
    Point center;
    double radius;
};

struct Square : IShapeAcceptor<Square>
{
    Square(const Point& topLeft, double sideLength) : topLeft(topLeft), sideLength(sideLength) {}    
    Point topLeft;
    double sideLength;
};

Не требуется дублировать код.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow