原先只是了解这个名词,想着C++20后静态多态直接用 concept来实现就好了就没细看,没必要整这些模板元编程的奇技淫巧。没想到面试某量化C++开发的时候被狠狠拷打…….

CRTP (curiously recurring template pattern)

一般认为,CRTP可以用来实现静态多态

template <typename T>
class Base {
  void func() {
    static_cast<T*>(this)->funcImpl();
  }
};

class Derived : public Base<Derived> {
  void funcImpl() {
    // do works here
  }
};

通过CRTP可以使得类具有类似于虚函数的效果,同时又没有虚函数调用时的开销(虚函数调用需要通过虚函数指针查找虚函数表进行调用),同时类的对象的体积相比使用虚函数也会减少(不需要存储虚函数指针),但是缺点是无法动态绑定,感觉有点过于鸡肋。

有什么用呢?可以用来向纯虚类一样做接口:(以下类似的代码在大量数学库中出现)

template <typename ChildType> 
struct VectorBase {
  ChildType &underlying() { return static_cast<ChildType &>(*this); }
  inline ChildType &operator+=(const ChildType &rhs) {
    this->underlying() = this->underlying() + rhs;
    return this->underlying();
  }
};
struct Vec3f : public VectorBase<Vec3f> {
  float x{}, y{}, z{};
  Vec3f() = default;
  Vec3f(float x, float y, float z) : x(x), y(y), z(z) {}
};

inline Vec3f operator+(const Vec3f &lhs, const Vec3f &rhs) {
  Vec3f result;
  result.x = lhs.x + rhs.x;
  result.y = lhs.y + rhs.y;
  result.z = lhs.z + rhs.z;
  return result;
}

定义好VectorBase后,Vec3f, Vec4f等直接实现接口就好。相比虚函数的形式,在空间和runtime都有优势。

enable_shared_from_this ?

另一个常见的用处是与 shared_ptr 配套的 std::enable_shared_from_this。若我们有一个用shared_ptr管理的资源,当我们想要接管该资源,返回一个接管 thisshared_ptr

// buggy
struct Bad {
  auto get() -> shared_ptr<Bad> {
    return std::shared_ptr<Bad>(this);
  }
};

TEST() {
  auto p1 = std::make_shared<Bad>();
  auto p2 = p1->get();
  assert(p2.use_cont() == 1);  // Unexpected!
}

上述代码错误的原因在于std::make_shared总是创建一个控制块,从而导致p1, p2虽然管理的对象相同,却并不知道彼此的存在。两者的 ref-cnt都是1,在析构的时候该对象会被析构两次。类似的bug还有 effective modern c++ Item19 中的例子:

std::vector<std::shared_ptr<Widge>> processedWidgets;

class Widget {
 public:
  ...
  void process() {
    ...  // process
    processedWidgets.emplace_back(this);  // buggy!
  }
};

正确的做法是

class Widget: public std::enable_shared_from_this<Widget> {
  void process() {
    ...  // process
    processedWidgets.emplace_back(std::shared_from_this());
  }
};

而这里的解决方案就是CRTP。实现大概就是借用一个 weak_ptr,在std::shared_from_this时候利用这个weak_ptr进行新的shared_ptr的拷贝构造,从而增加引用计数。

with deducing this

c++23 引入了deducing this 这可以“简化”我们的CRTP模式,使得我们不需要显式地传递模板参数。

// C++17
template <typename Derived>
struct add_postfix_increment {
  Derived operator++(int) {
    auto& self = static_cast<Derived&>(*this);
    Derived tmp(self);
    ++self;
    return tmp;
  }
};

struct some_type : add_postfix_increment<some_type> {
  some_type& operator++() { ... }
};

with deducing this

struct add_postfix_increment {
  template <typename Self>
  auto operator++(this Self&& self, int) {
    auto tmp = self;
    ++self;
    return tmp;
  }
};

struct some_type : add_postfix_increment {
  some_type& operator++() { ... }
};

相对来说看起来自然多了。当然,deducing this的主要作用不是为了简化CRTP,具体可以看原始的proposal,结合optional<T>的实现。(另外g++13竟然还不支持deducing this :( )