是否存在类声明虚方法而编译器不需要使用 vptr 的情况?
我想知道是否有可能的优化,即使对象的类型是具有虚拟方法的类,编译器也不需要将 vptr 分配给实例化对象。
例如,考虑一下:
#include <iostream>
struct FooBase
{
virtual void bar()=0;
};
struct FooDerived : public FooBase
{
virtual void bar() { std::cout << "FooDerived::bar()\n"; }
};
int main()
{
FooBase* pFoo = new FooDerived();
pFoo->bar();
return 0;
}
在这个例子中,编译器肯定知道编译时 pFoo 的类型是什么,所以它不需要为 pFoo 使用 vptr,对吧? 是否有更有趣的情况,编译器可以避免使用 vptr?
I was wondering if there is a possible optimization where the compiler does not need to assign a vptr to an instantiated object even though the object's type is a class with virtual methods.
For example consider:
#include <iostream>
struct FooBase
{
virtual void bar()=0;
};
struct FooDerived : public FooBase
{
virtual void bar() { std::cout << "FooDerived::bar()\n"; }
};
int main()
{
FooBase* pFoo = new FooDerived();
pFoo->bar();
return 0;
}
In this example the compiler surely knows what will be the type of pFoo at compile time, so it does not need to use a vptr for pFoo, right?
Are there more interesting cases where the compiler can avoid using a vptr?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
构建于 Andrew Stein的回答,因为我想你也想知道什么时候可以避免所谓的“虚拟函数的运行时开销”。 (开销是有的,但是很小,而且很少值得担心。)
确实很难避免vtable指针的空格,但是指针本身可以忽略,包括在你的例子中。因为 pFoo 的初始化是在该范围内,所以编译器知道
pFoo->bar
必须表示 FooDerived::bar,并且不会不需要检查vtable。还有几种缓存技术可以避免多次 vtable 查找,从简单到复杂。Building on Andrew Stein's answer, because I think you also want to know when the so-called "runtime overhead of virtual functions" can be avoided. (The overhead is there, but it's tiny, and rarely worth worrying about.)
It's really hard to avoid the space of the vtable pointer, but the pointer itself can be ignored, including in your example. Because pFoo's initialization is in that scope, the compiler knows that
pFoo->bar
must mean FooDerived::bar, and doesn't need to check the vtable. There are also several caching techniques to avoid multiple vtable lookups, ranging from the simple to the complex.即使在您展示的情况下,我怀疑任何编译器都会执行您的建议。
您正在使用一个非常简单的程序,所有程序都在一个文件中。
想象一下,您在 Foo.h 和 Foo.cpp 中有 FooBase 和 FooDerived,在 main.cpp 中有 main。当编译Foo.cpp时,编译器如何知道整个程序中不需要vtbl。还没有看到main.cpp。
最终确定只能在链接时做出,此时修改目标文件、查找所有对 sizeof(FooDerived) 的调用等都为时已晚且复杂。
Even in the case you show, I doubt that any compiler will do what you suggest.
You are using a very simple program all in one file.
Imagine that you had FooBase and FooDerived in Foo.h and Foo.cpp and main in main.cpp. When compiling Foo.cpp, how is the compiler to know that in the whole program there is no need for a vtbl. It has not seen main.cpp.
The final determination can only be made at link time, when it is way too late and complicated to modify the object file, find all the calls to sizeof(FooDerived), etc.
即使是最现代的、全局优化的编译器仍然坚持“独立翻译”的原则。根据这一原则,每个翻译单元都是独立编译的,无需了解整个最终程序中可能存在的任何其他翻译单元。
当您声明具有外部链接的类时,该类可以在其他翻译单元中使用。编译器不知道您的类如何在其他翻译单元中使用。因此,它必须假设该类的每个实例通常可能需要在构造时正确初始化其虚拟表指针。
在您的示例中,智能编译器可能会发现
*pFoo
对象的动态类型是FooDerived
。这将允许编译器优化代码:生成对FooDerived::bar
函数的直接调用,以响应pFoo->bar()
表达式(不使用虚拟表)。但尽管如此,编译器通常仍然必须正确初始化每个 FooDerived 对象中的虚拟表指针。Even the most modern, globally-optimizing compilers are still sticking to the principle of "independent translation". According to this principle, every translation unit is compiled independently, without any knowledge about any other translation units that might exist in the entire final program.
When you declare a class with external linkage, this class can be used in other translation units. The compiler has no idea about how your class might be used in those other translation units. So, it has to assume that every instance of that class might in general need its virtual table pointer initialized properly at the moment of construction.
In your example a smart compiler might figure out that the dynamic type of
*pFoo
object isFooDerived
. That would allow the compiler to optimize the code: to generate a direct call toFooDerived::bar
function in response topFoo->bar()
expression (without using the virtual table). But nevertheless, the compiler normally would still have to properly initialize the virtual table pointer in eachFooDerived
object.当编译器知道将调用哪个确切类的方法时,它可以消除虚拟函数调用的间接开销。这是优化编译器进行的一种指向分析的结果。它们跟踪指针变量的数据流,并且任何地方指针只能指向一种类型的对象,它都可以生成针对该类型的调用,静态地(即在编译时)实现虚拟调用语义。
正如许多其他人所说,除非编译器正在进行整个程序/链接时优化(变得越来越流行;GCC 在 4.5 中获得了它),否则它仍然需要生成某种虚拟函数表来支持单独编译。
It is possible for a compiler to cut out the indirection overhead of virtual function calls when it knows which exact class's method is going to be invoked. This is the result of a form of points-to analysis that optimizing compilers do. They trace the data flow of pointer variables, and any place a pointer can only point to objects of one type, it can generate calls for exactly that type, implementing virtual call semantics statically (i.e. at compile time).
As many others have said, unless the compiler is doing whole-program/link-time optimization (becoming more popular; GCC gains it in 4.5), it still needs to generate some sort of table of virtual functions to support separate compilation.