跳至內容

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

已知用途

[編輯 | 編輯原始碼]

巴頓-納克曼技巧

[編輯 | 編輯原始碼]

維基百科上的奇妙遞迴模板模式

華夏公益教科書