在C++中使用接口(抽象基类)是否会导致运行时性能降低?
在C++中使用接口(抽象基类)是否会导致运行时性能降低?
简短回答:不会。
长回答: 一个类的基类或祖先数并不会影响它的速度,唯一影响速度的是方法调用的成本。
非虚方法调用有成本(但可以内联)
虚方法调用的成本稍高,因为你需要在调用之前查找要调用的方法(但这只是一个简单的表查找,而不是搜索)。由于接口上的所有方法都是虚的,因此会有这种成本。
除非您正在编写某些超级敏感的应用程序,否则这不应该是问题。使用接口所带来的额外清晰度通常可以弥补任何感知到的速度降低。
有一种很容易被忽视的虚函数惩罚是:虚函数调用在对象类型不知道编译时(常见情况下)时不会被内联。如果您的函数很小并且适合内联,这种惩罚可能非常显著,因为不仅会增加调用开销,而且编译器在优化调用函数方面也受到限制(它必须假设虚函数可能已更改某些寄存器或内存位置,它不能在调用者和被调用者之间传播常量值)。
就与普通函数调用相比的调用开销而言,答案取决于目标平台。如果您的目标是带有x86 / x64 CPU的PC,则调用虚函数的惩罚非常小,因为现代x86 / x64 CPU可以对间接调用进行分支预测。但是,如果您的目标是PowerPC或一些其他RISC平台,则虚函数调用惩罚可能相当大,因为在某些平台上从不预测间接调用(参见PC/Xbox 360跨平台开发最佳实践)。
foo()
的调用内联到Base* x = new Derived; x->foo();
中。 - j_random_hacker与常规调用相比,每个虚函数调用会有一个小的惩罚。除非每秒进行数十万次调用,否则您不太可能观察到差异,通常为了增加代码清晰度而支付的代价通常是值得的。
在某些情况下适用的另一种选择是使用模板进行编译时多态性。例如,当您想要在程序开始时进行实现选择,然后在执行期间使用它时,这将非常有用。以下是一个使用运行时多态性的示例:
class AbstractAlgo
{
virtual int func();
};
class Algo1 : public AbstractAlgo
{
virtual int func();
};
class Algo2 : public AbstractAlgo
{
virtual int func();
};
void compute(AbstractAlgo* algo)
{
// Use algo many times, paying virtual function cost each time
}
int main()
{
int which;
AbstractAlgo* algo;
// read which from config file
if (which == 1)
algo = new Algo1();
else
algo = new Algo2();
compute(algo);
}
class Algo1
{
int func();
};
class Algo2
{
int func();
};
template<class ALGO> void compute()
{
ALGO algo;
// Use algo many times. No virtual function cost, and func() may be inlined.
}
int main()
{
int which;
// read which from config file
if (which == 1)
compute<Algo1>();
else
compute<Algo2>();
}
大多数人都会注意到运行时间的惩罚,这是正确的。
然而,在我从事大型项目开发的经验中,清晰接口和适当封装带来的好处很快抵消了速度上的优势。模块化的代码可以被替换为改进后的实现,因此净效果是巨大的收益。
个人结果可能有所不同,并且显然取决于您正在开发的应用程序。
需要注意的是,虚函数调用的成本可能因平台而异。在游戏机上,它们可能更加明显,因为通常 vtable 调用意味着缓存未命中,会影响分支预测。
在C++中使用抽象基类通常需要使用虚函数表,所有接口调用都将通过该表进行查找。与原始函数调用相比,成本微不足道,因此请确保您需要更快的速度之前再担心它。