更多 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();
};