奇怪的重複模板模式(CRTP)

CRTP 是虛擬函式和傳統繼承的強大靜態替代方法,可用於在編譯時為型別屬性提供屬性。它通過一個基類别範本來工作,該模板將派生類作為其模板引數之一。這允許它合法地執行其 this 指向派生類的 static_cast

當然,這也意味著 CRTP 類必須始終用作其他類的基類。派生類必須將自己傳遞給基類。

Version >= C++ 14

假設你有一組容器,它們都支援 begin()end() 的功能。標準庫對容器的要求需要更多功能。我們可以設計一個 CRTP 基類來提供該功能,僅基於 begin()end()

#include <iterator>
template <typename Sub>
class Container {
  private:
    // self() yields a reference to the derived type
    Sub& self() { return *static_cast<Sub*>(this); }
    Sub const& self() const { return *static_cast<Sub const*>(this); }

  public:
    decltype(auto) front() {
      return *self().begin();
    }

    decltype(auto) back() {
      return *std::prev(self().end());
    }

    decltype(auto) size() const {
      return std::distance(self().begin(), self().end());
    }

    decltype(auto) operator[](std::size_t i) {
      return *std::next(self().begin(), i);
    }
};

上面的類為任何提供 begin()end() 的子類提供了 front()back()size()operator[] 的函式。示例子類是一個簡單的動態分配陣列:

#include <memory>
// A dynamically allocated array
template <typename T>
class DynArray : public Container<DynArray<T>> {
  public:
    using Base = Container<DynArray<T>>;

    DynArray(std::size_t size)
      : size_{size},
      data_{std::make_unique<T[]>(size_)}
    { }

    T* begin() { return data_.get(); }
    const T* begin() const { return data_.get(); }
    T* end() { return data_.get() + size_; }
    const T* end() const { return data_.get() + size_; }

  private:
    std::size_t size_;
    std::unique_ptr<T[]> data_;
};

DynArray 類的使用者可以輕鬆使用 CRTP 基類提供的介面,如下所示:

DynArray<int> arr(10);
arr.front() = 2;
arr[2] = 5;
assert(arr.size() == 10);

實用性: 此模式特別避免了在執行時進行的虛擬函式呼叫,這些函式遍歷遍歷繼承層次結構並且僅依賴於靜態強制轉換:

DynArray<int> arr(10);
DynArray<int>::Base & base = arr;
base.begin(); // no virtual calls

基類 Container<DynArray<int>> 中函式 begin() 中唯一的靜態轉換允許編譯器大幅優化程式碼,並且在執行時不會發生虛擬表查詢。

限制: 因為基類是模板化的,並且對於兩個不同的 DynArrays 是不同的,所以不可能在型別同構陣列中儲存指向它們的基類的指標,因為通常可以使用正常繼承,其中基類不依賴於派生型別:

class A {};
class B: public A{};

A* a = new B;