编译器通常不能用静态调用替换运行时决策的原因有很多,主要原因是编译器涉及编译时不可用的信息,例如配置或用户输入。除此之外,我想指出另外两个原因,这通常是不可能的。
首先,C ++编译模型基于单独的编译单元。编译一个单元时,编译器仅知道正在编译的源文件中定义的内容。考虑一个具有基类的编译单元,以及一个引用了基类的函数:
struct Base {
virtual void polymorphic() = 0;
};
void foo(Base& b) {b.polymorphic();}
单独编译时,编译器不了解实现的类型,Base
因此无法删除动态调度。这也不是我们想要的东西,因为我们希望能够通过实现接口来用新功能扩展程序。可能在链接时这样做,但前提是必须假定程序已完全完成。动态库可能会打破这种假设,并且如以下所示,总是有一些情况是根本不可能的。
一个更根本的原因来自可计算性理论。即使有完整的信息,也无法定义一种算法来计算是否要到达程序中的特定行。如果可以解决暂停问题:对于一个程序P
,我P'
通过在末尾添加一行来创建一个新程序。P
。该算法现在将能够确定是否达到该行,从而解决了停止问题。
通常无法确定意味着编译器无法总体上确定将哪个值分配给变量,例如
bool someFunction( ) {
}
Base* b = nullptr;
if (someFunction( ... ))
b = new Derived1();
else
b = new Derived2();
b->polymorphicFunction();
即使所有参数在编译时都是已知的,也无法一般地证明将采用该程序的哪条路径以及哪种静态类型b
。可以并且可以通过优化编译器来进行近似,但是在某些情况下,它不起作用。
话虽如此,C ++编译器非常努力地删除动态分配,因为它打开了许多其他优化机会,主要是因为它们能够通过代码内联和传播知识。如果您有兴趣,则可以找到有关GCC虚拟化实现的有趣博客文章。