【发布时间】:2015-09-22 01:30:18
【问题描述】:
考虑以下一段 C++ 代码:
class IFoo {
public:
virtual void Bar() const = 0;
};
template <typename Derived>
class AbstractFoo : public IFoo {
public:
void Bar() const override {
int i = 0;
auto derived = static_cast<const Derived *>(this);
while (derived->ShouldBar(i++)) {
derived->DoBar();
}
}
};
class FooImpl : public AbstractFoo<FooImpl> {
private:
bool ShouldBar(int i) const {
return i < 10;
}
void DoBar() const {
std::cout << "Bar!" << std::endl;
}
friend class AbstractFoo<FooImpl>;
};
int main() {
std::unique_ptr<IFoo> foo(new FooImpl());
foo->Bar();
}
当然,这是 curiously recurring template pattern 有一点点扭曲:在虚拟方法 Bar 通过接口 IFoo 多态地调度一次之后,对 ShouldBar 和 DoBar 的调用保持静态,甚至可能内联。如果以另一种方式实现(AbstractFoo 是非泛型的,ShouldBar 和 DoBar 私有虚拟方法),每次迭代都会有两个虚拟函数调用。
这种优化机会问题的情况包括迭代方案,例如巨大状态空间的depth-first search 和saturation。在这些算法的某些点上,具体实现必须选择继续搜索的方向、是否将状态添加到结果集等。多态实现,这些可能导致数百万次虚拟调用相对较小的函数(其中一些甚至可能是空的!),它的性能损失甚至可以通过分析来衡量。 (请记住,这些迭代算法通常不执行 I/O,这与上面的玩具示例相反。)
在没有 CRTP 的语言中,唯一的替代解决方案是重复迭代方案的“骨架”。例如,在 C# 中,这并不太痛苦,因为我们有部分方法:
interface IFoo {
void Bar();
}
// This is copy-pasted for every IFoo implementation.
partial class FooImpl : IFoo {
void Bar() {
int i = 0;
bool shouldBar = false;
ShouldBar(i++, out shouldBar);
while (shouldBar) {
DoBar();
ShouldBar(i++, out shouldBar);
}
}
partial void ShouldBar(int i, out bool result);
partial void DoBar();
}
partial class FooImpl {
partial void ShouldBar(int i, our bool result) {
result = i < 10;
}
partial void DoBar() {
Console.WriteLine("Bar!");
}
}
如你所见,还是有一些尴尬,因为部分方法必须返回void,并且需要复制“抽象”类的代码。
是否有任何语言/运行时环境可以对简单的虚拟保护方法执行此优化?
我认为问题归结为虚拟公共方法不应该为每个实现生成机器代码,而是为每个具体类。考虑一个简单的vtable,FooImpl 的vtable 中的插槽不应该在IFoo#Bar 的插槽中保存AbstractFoo#Bar,而是一个专门的FooImpl#Bar,对ShouldBar 和DoBar 进行非虚拟/内联调用由 JIT 生成。
是否有任何环境能够执行这种优化,或者至少在这个方向上进行一些研究?
【问题讨论】:
-
既然 C++ 拥有一切所需,为什么还要寻找其他语言?
-
虽然 C++ 非常适合(几乎)零成本的抽象,但像 CRTP 这样的东西很难被称为直观。此外,分代垃圾收集器的优点是无法用 RAII 轻松模拟(例如,分摊小对象的分配和释放成本)并且需要托管运行时。虽然 perharps,但我最好还是使用 C++/CLI ... :)
标签: inheritance compiler-optimization jit virtual-functions crtp