我知道虚函数本质上是vtable中包含的函数指针,由于间接等原因使得多态调用变慢。 但是当呼叫是确定性的时候,我想知道编译器优化。 确定性,我指的是以下情况:
struct Foo
{
virtual void DoSomething(){....}
};
int main()
{
Foo myfoo;
myfoo.DoSemthing();
return 0;
}
struct Foo
{
virtual void DoSomething();
};
struct Bar : public Foo
{
virtual void DoSomething();
};
int main()
{
Foo* a = new Foo();
a->DoSomething(); //Overhead ? a doesn't seem to be able to change nature.
Foo* b = new Bar();
b->DoSomething(); //Overhead ? It's a polymorphic call, but b's nature is deterministic.
Bar* c = new Bar();
c->DoSomething(); //Overhead ? It is NOT possible to have an other version of the method than Bar::DoSomething
return 0;
}
答案 0 :(得分:9)
在第一种情况下,这不是虚拟呼叫。编译器将直接向Foo::DoSomething()
发出呼叫。
在第二种情况下,它更复杂。首先,它最好是链接时间优化,因为对于特定的转换单元,编译器不知道还有谁可能从该类继承。您遇到的另一个问题是共享库,如果没有您的可执行文件知道任何相关内容,它们也可能继承。
一般而言,这是一种编译器优化,称为虚函数调用消除或 devirtualization ,并且在某种程度上是一个活跃的研究领域。有些编译器在某种程度上做到了,有些编译器根本没做。
请参阅GCC(g ++),-fdevirtualize
和-fdevirtualize-speculatively
。这些名字暗示着保证的质量水平。
答案 1 :(得分:2)
在Visual Studio 2013中,即使行为是确定性的,也不会优化虚函数调用。
例如,
#include <iostream>
static int counter = 0;
struct Foo
{
virtual void VirtualCall() { ++counter; }
void RegularCall() { ++counter; }
};
int main()
{
Foo* a = new Foo();
a->VirtualCall(); //Overhead ? a doesn't seem to be able to change nature.
a->RegularCall();
std::cout << counter;
return 0;
}
虚拟呼叫的机器代码如下所示:
a->VirtualCall()
0001b 8b 01 mov eax, DWORD PTR [ecx]
0001d ff 10 call DWORD PTR [eax]
常规调用的机器代码显示函数是内联的 - 没有函数调用:
a->RegularCall()
00 inc DWORD PTR _counter