原先只是了解这个名词,想着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管理的资源,当我们想要接管该资源,返回一个接管 this
的 shared_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 :( )