C++ CRTP
奇异递归模板模式(Curiously Recurring Template Pattern)是 C++ 模板的一个技巧。
CRTP 本质是因为 C++ 的类模板和类可以互相继承,这使得类模板的参数化类型可以使用派生类。由于模板是在实例化时才确定的,这使得基类可以知道派生类的类型,从而可以通过基类的接口转发派生类的接口。
template <typename T>
struct Base
{
};
struct Derived : public Base<Derived>
{
};
template <typename T>
struct Base
{
void interface()
{
static_cast<T&>(*this).implementation();
}
};
struct Derived : public Base<Derived>
{
void implementation()
{
std::cout << "Derived impl" << std::endl;
}
};
int main()
{
Derived d;
d.interface();
}
除了使基类可以调用派生类接口以外,CRTP 还可以通过函数模板的隐式特化实现编译期静态选择:
template <typename T>
struct Base
{
void interface()
{
static_cast<T&>(*this).interface();
}
};
struct Derived : public Base<Derived>
{
void interface()
{
std::cout << "Derived interface" << std::endl;
}
};
template<typename T>
void foo(Base<T>& t)
{
t.interface();
}
int main()
{
Derived d;
foo(d);
}
CRTP 的一个广泛的用途是自动生成运算符:二元复合算数运算符(+=
,-=
,/=
etc.)可以根据二元算术运算符(+
,-
,/
)实现,那么就可以设计一个基类模板,其中定义复合算数运算符的重载,同时这些重载函数内会调用派生类的算术运算符。这使得派生类只需要继承该基类,并且实现算术运算符,就可以自动获得复合算术运算符。
同理,不等运算也可也从相等运算生成,但 C++20 开始编译器已经可以自动执行此过程,所以 C++20 起不需要该种方式。
std::enable_shared_from_this
就是对 CRTP 的一个经典使用:
std::enable_shared_from_this
能让其一个对象(假设其名为 t
,且已被一个 std::shared_ptr
对象 pt
管理)安全地生成其他额外的 std::shared_ptr
实例(假设名为 pt1
,pt2
,… ) ,它们与 pt
共享对象 t
的所有权。
若一个类 T
继承 std::enable_shared_from_this<T>
,则会为该类 T
提供成员函数 shared_from_this
。当 T
类型对象 t
被一个为名为 pt
的 std::shared_ptr<T>
类对象管理时,调用 T::shared_from_this
,将会返回一个新的 std::shared_ptr<T>
,它与 pt
共享 t
的所有权。
其内部保存着一个对 this
的弱引用(例如 std::weak_ptr
)。 std::shared_ptr
的构造函数检测无歧义且可访问的 (C++17 起) enable_shared_from_this
基类,并且若内部存储的弱引用未为生存的 std::shared_ptr
占有,则 (C++17 起) 赋值新建的 std::shared_ptr
为内部存储的弱引用。为已为另一 std::shared_ptr
所管理的对象构造一个 std::shared_ptr
,将不会考虑内部存储的弱引用,从而将导致未定义行为。