CRTP 编译时多态性的优点和用例是什么?
What are the adavantages and use cases of CRTP compiletime polymorphism?
我看到我们可以使用 CRTP 引入某种编译时多态性,但是我想知道这比旧的虚函数有什么好处。最后我们必须调用 static_cast<const T*>(this)->implementation();
,这是一个间接级别,就像 vtable 所做的那样。
它们有何不同?有什么优势吗?我只看到它们不能被基类破坏的缺点。对我来说,它作为学术考试看起来不错,但以常规多态性为主。
原因是性能。
static_cast<const T*>(this)->implementation();
在编译时解析为相应 T::implementation()
重载的地址:
CALL <fixed-address>
另一方面,虚拟成员调用通常在 运行 时通过 vtable 中的偏移量使用间接调用来解析。在最简单的情况下,优化器可以将其转换为编译时,但没有办法可靠地做到这一点。所以通常你可以期望代码看起来像这样:
MOV rax, <vtable-ptr>
MOV rax, [rax+<offset>] ; Indirection via a vtable
CALL rax
由于对目标地址的数据依赖,这种类型的调用很可能会在第一次调用时出现管道停顿,而后续调用将在很大程度上依赖于分支预测器的质量。
另一方面,静态调用非常快,因为它不涉及管道停顿。
我看到我们可以使用 CRTP 引入某种编译时多态性,但是我想知道这比旧的虚函数有什么好处。最后我们必须调用 static_cast<const T*>(this)->implementation();
,这是一个间接级别,就像 vtable 所做的那样。
它们有何不同?有什么优势吗?我只看到它们不能被基类破坏的缺点。对我来说,它作为学术考试看起来不错,但以常规多态性为主。
原因是性能。
static_cast<const T*>(this)->implementation();
在编译时解析为相应 T::implementation()
重载的地址:
CALL <fixed-address>
另一方面,虚拟成员调用通常在 运行 时通过 vtable 中的偏移量使用间接调用来解析。在最简单的情况下,优化器可以将其转换为编译时,但没有办法可靠地做到这一点。所以通常你可以期望代码看起来像这样:
MOV rax, <vtable-ptr>
MOV rax, [rax+<offset>] ; Indirection via a vtable
CALL rax
由于对目标地址的数据依赖,这种类型的调用很可能会在第一次调用时出现管道停顿,而后续调用将在很大程度上依赖于分支预测器的质量。
另一方面,静态调用非常快,因为它不涉及管道停顿。