我设置了一个(可能非常不科学的)小测试来确定单级单继承中虚函数的开销,我得到的结果,好吧,在多态访问派生类或访问派生类时完全相同直接地。有点令人惊讶的是当任何函数被声明为虚拟时引入的计算时间的数量级(见下面的结果)。
这样声明成员函数时是否有这么多开销,为什么即使直接访问派生类也仍然存在?
代码如下:
class base
{
public:
virtual ~base() {}
virtual uint func(uint i) = 0;
};
class derived : public base
{
public:
~derived() {}
uint func(uint i) { return i * 2; }
};
uint j = 0;
ulong k = 0;
double l = 0;
ushort numIters = 10;
base* mybase = new derived; // or derived* myderived = ...
for(ushort i = 0; i < numIters; i++)
{
clock_t start2, finish2;
start2 = clock();
for (uint j = 0; j < 100000000; ++j)
k += mybase->func(j);
finish2 = clock();
l += (double) (finish2 - start2);
std::cout << "Total duration: " << (double) (finish2 - start2) << " ms." << std::endl;
}
std::cout << "Making sure the loop is not optimized to nothing: " << k << std::endl;
std::cout << "Average duration: " << l / numIters << " ms." << std::endl;
结果:
base* mybase = new derived;
给出平均约 338 毫秒。
derived* myderived = new derived;
给出平均约 338 毫秒。
消除继承和去除虚函数平均减少约 38 毫秒。
几乎减少了 10 倍!所以基本上,如果任何函数被声明为虚函数,开销将始终存在,即使我不以多态方式使用它也是如此?
谢谢。
最佳答案
“直接”访问它与“间接”访问它所做的工作相同。
当您调用 myderived
上的函数时,存储在那里的指针可能指向从 derived
派生的某个类的某个对象。编译器不能假定它真的是一个derived
对象,它可能是一个覆盖虚函数的进一步派生类的对象,所以需要像 mybase
案例。在这两种情况下,函数在调用之前都会在虚函数表中查找。
要以非多态方式调用函数,请不要使用指针:
derived myderived;
myderived.func(1);
当您删除虚函数时,编译器可以内联函数调用,这样您基本上就可以得到一个简单的循环:
for (uint j = 0; j < 100000000; ++j)
k += i * 2;
这要快得多,因为您节省了 100000000 次函数调用的开销,而且编译器甚至可以进一步优化循环,如果其中有函数调用则不会。
另请注意,如果函数做了一些实际工作,则内联版本和虚函数调用之间的差异会小得多。在此示例中,函数体几乎不占用任何时间,因此调用函数的成本超过了执行函数体的成本。
关于c++ - 测试虚函数的开销,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3140088/