C++
不思議な反復テンプレートパターン(CRTP)
サーチ…
前書き
クラスがクラステンプレートからテンプレートパラメータの1つとして継承するパターン。 CRTPは通常、C ++で静的多型を提供するために使用されます。
奇妙に繰り返されるテンプレートパターン(CRTP)
CRTPは、コンパイル時に型のプロパティを与えるために使用できる、仮想関数と従来の継承に対する強力で静的な代替手段です。これはテンプレートパラメータの1つとして派生クラスを取る基本クラステンプレートを持つことによって機能します。これにより、派生クラスへのthis
ポインタのstatic_cast
を合法的に実行することができます。
もちろん、これは、CRTPクラスが他のクラスの基本クラスとして常に使用されなければならないことを意味します。そして、派生クラスはそれ自身を基本クラスに渡す必要があります。
関数begin()
とend()
をすべてサポートするコンテナのセットがあるとしましょう。コンテナの標準ライブラリの要件には、より多くの機能が必要です。 begin()
とend()
のみに基づいて、その機能を提供するCRTP基本クラスを設計することができます:
#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);
}
};
上記のクラスは、 begin()
およびend()
を提供begin()
サブクラスの関数front()
、 back()
、 size()
およびoperator[]
を提供しbegin()
。サブクラスの例は、単純に動的に割り当てられた配列です。
#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
基本クラスContainer<DynArray<int>>
begin()
関数内で唯一の静的キャストは、コンパイラがコードを大幅に最適化することを可能にし、実行時に仮想テーブルの参照は行われません。
制限事項:基本クラスはテンプレート化されており、2つの異なるDynArray
異なるため、基本クラスが派生クラスに依存しない通常の継承で一般的にできるように、型同型配列内の基本クラスへのポインタを格納することはできませんタイプ:
class A {};
class B: public A{};
A* a = new B;
コードの重複を避けるためのCRTP
Visitor Patternの例では、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;
};
重複したコードは必要ありません。