奇怪的重复模板模式(CRTP)
CRTP 是虚函数和传统继承的强大静态替代方法,可用于在编译时为类型属性提供属性。它通过一个基类模板来工作,该模板将派生类作为其模板参数之一。这允许它合法地执行其 this
指向派生类的 static_cast
。
当然,这也意味着 CRTP 类必须始终用作其他类的基类。派生类必须将自己传递给基类。
Version >= 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);
}
};
上面的类为任何提供 begin()
和 end()
的子类提供了 front()
,back()
,size()
和 operator[]
的函数。示例子类是一个简单的动态分配数组:
#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()
中唯一的静态转换允许编译器大幅优化代码,并且在运行时不会发生虚拟表查找。
限制: 因为基类是模板化的,并且对于两个不同的 DynArray
s 是不同的,所以不可能在类型同构数组中存储指向它们的基类的指针,因为通常可以使用正常继承,其中基类不依赖于派生类型:
class A {};
class B: public A{};
A* a = new B;