更多 C++ 惯用法/奇妙递归模板模式
出现
使用派生类作为模板参数来专门化基类。
- CRTP
- 从上而下的混合
- 静态多态性
- 模拟动态绑定
- 颠倒继承
为了在基类中提取与类型无关但可定制类型的功能,并将该接口/属性/行为混合到派生类中,并针对派生类进行定制。
在 CRTP 惯用法中,类 T 继承自专门化于 T 的模板。
class T : public X<T> {…};
只有在可以独立于 T 确定 X<T> 的大小的情况下,这才是有效的。通常,基类模板将利用这样一个事实:成员函数体(定义)直到其声明很久之后才会被实例化,并且将在其自己的成员函数内使用派生类的成员,通过使用 static_cast
,例如:
template <class Derived>
struct base
{
void interface()
{
// ...
static_cast<Derived*>(this)->implementation();
// ...
}
static void static_interface()
{
// ...
Derived::static_implementation();
// ...
}
// The default implementation may be (if exists) or should be (otherwise)
// overridden by inheriting in derived classes (see below)
void implementation();
static void static_implementation();
};
// The Curiously Recurring Template Pattern (CRTP)
struct derived_1 : base<derived_1>
{
// This class uses base variant of implementation
//void implementation();
// ... and overrides static_implementation
static void static_implementation();
};
struct derived_2 : base<derived_2>
{
// This class overrides implementation
void implementation();
// ... and uses base variant of static_implementation
//static void static_implementation();
};
C++23 添加了一项名为显式对象参数的新功能,允许您将调用方法的对象作为方法的第一个参数传递,而不是 *this
。在调用方法时,与所有函数调用一样,只有在必要时才执行向上转型,从而可以通过使用方法模板或 auto
使得在不覆盖的情况下更改派生类的方法行为成为可能。如果您要编写的 CRTP 基类只有非静态方法,则无需将 T 作为模板参数传递,使其类似于从普通基类派生。
class T : public X {…}; // note: X instead of X<T>
这种方法的一个好处是,如果 T
是 Derived
的基类,则 Derived
的混合方法将使用 Derived
方法而不是 T
方法。另一个好处是,它使 const 和非 const 方法重载变得不必要,因为显式对象参数可以绑定到 const 和非 const 引用,如果约束没有用于防止这种情况。
#include <type_traits>
#include <utility>
struct base
{
// When calling `interface` on a derived class, a reference to derived class
// is passed in as `val` instead of upcasting to `base&` or `base&&`
void interface(this auto&& val)
// Constraint prevents const call, `auto&&` explicit object parameter could
// bind to const references if this method was unconstrained
requires (!std::is_const_v<std::remove_reference_t<decltype(val)>>)
{
// ...
// Calls rvalue overload if val is a temporary
std::forward<decltype(val)>(val).implementation();
// ...
}
};
// Note that `base` is derived from instead of `base<derived_1>`
struct derived_1 : base
{
void implementation();
};
struct derived_2 : derived_1
{
// Calling `derived_2::interface` will call `derived_2::implementation`
// because `derived_1` was not passed into `base`, whereas if `base` was a
// traditional CRTP class template `derived_1` would inherit from
// `base<derived_1>` and `derived_2::interface` would
// `static_cast<derived_1*>` and call `derived_1::implementation`
void implementation();
};