跳至内容

更多 C++ 惯用法/奇妙递归模板模式

来自 Wikibooks,开放世界中的开放书籍

奇妙递归模板模式

[编辑 | 编辑源代码]

使用派生类作为模板参数来专门化基类。

也称为

[编辑 | 编辑源代码]
  • 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>

这种方法的一个好处是,如果 TDerived 的基类,则 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();
};

已知用途

[编辑 | 编辑源代码]

巴顿-纳克曼技巧

[编辑 | 编辑源代码]

维基百科上的奇妙递归模板模式

华夏公益教科书