2016-03-22 59 views
0

我知道这里有很多关于vtables的问题,但我仍然有点困惑。Vtables只能用于指向基类的指针

只有当我们有一个指向基类的指针来解析派生类的哪个虚函数调用时,vtable才会被使用吗?

在我的例子中,在案例1中,在运行时是否使用了vtables,即使这个Tiger对象没有在堆/免费存储上动态创建?

在情况2中,是否使用了vtable,即使编译器知道我们正在指向一个Tiger对象。

情况3呢?

在此先感谢。

#include <iostream> 

using namespace std; 

class Animal // base class 
{ 
    public: 
     virtual void makeNoise() {cout<<" "<<endl;} 
}; 

class Tiger: public Animal 
{ 
    public: 
     void makeNoise() {cout<<"Tiger Noise"<<endl;} 
}; 

class Elephant: public Animal 
{ 
    public: 
     void makeNoise() {cout<<"Elephant Noise"<<endl;} 
}; 

int main() 
{ 
    //case 1 
    Tiger t1; 
    Animal* aptr = &t1; 
    aptr->makeNoise(); // vtables used? 

    //case 2 
    Tiger t2; 
    Tiger* tptr = &t2; //vtables used ? 
    tptr->makeNoise(); 

    //case 3 
    Elephant e1;  //vtables used ? 
    e1.makeNoise(); 

} 
+0

如果通过类似于案例1的父类指针调用虚拟方法,将使用vtable。 – user2061057

+0

@ user2061057 ...或父类引用... –

+1

由编译器决定是使用还是不使用vtable在每个特定的情况下。如果编译器可以证明指向对象的派生类型是什么,那么在此调用中不使用vtable是完全可以的。 –

回答

4

无论特定的编译器使用一个虚函数表或一个完全不同的机制来实现动态虚拟函数分派达到该编译器的内部实现。如果您想要了解特定编译器行为的答案,请查阅该编译器的文档和/或源代码。

C++语言本身定义了虚拟函数调用的工作方式,并且让编译器自己完成。

标准要求的是,根据调用该函数的对象的动态类型,将虚拟函数的调用分派给最终的覆盖。在您的代码中,t1t2的动态类型为Tiger,而动态类型e1Elephant

是的,大多数(如果不是全部的话)编译器使用虚函数表来实现虚函数调用。是的,任何像样的编译器都应该尽可能在编译时解决动态分派的问题,如果能够这样做的话,可以直接调用virtual-table-usage(这是编译器的实现质量问题)。

您示例中的哪些调用将被静态调度取决于编译器的优化器是多么“积极”(或“聪明”,如果您愿意的话)。

我会说每个理智的编译器都应该通过e1静态调用调用,即使禁用了优化。这将是一个完全不必要的悲观来调用那里的动态调度机制。

至于通过aptrtptr的叫声,这取决于你的编译器的优化器的静态分析仪是否能够消除aptrtptr的,在使用它们指向(实际对象替换它们,因为这些信息是缴费在编译时间)。一个体面的优化器应该能够做到这一点,并静态分配所有3个电话。

为了确保编译器如何处理调用,请检查生成的程序集。

0

正如其他评论所说,vtables的使用是由编译器处理的,编译器可以尝试优化他们的访问权限,只要产生的输出是预期的输出即可。

但是,我们可以将vtables看作包含虚拟方法地址的表。每次调用父类中声明为“虚拟”的方法时,都应在运行时检查vtable,以便知道跳转的具体地址。

这是程序员期望的行为,尽管具体的机制可能会更棘手,甚至可能根本不依赖查询vtables,如果编译器可以在编译时确定地址。

因此,在所有这些情况下,编译器可能足够聪明以在编译时设置地址。但是,在“最糟糕的情况下”你应该依赖这个,因为你调用虚拟方法 - 这是预期的行为 - 并且让编译器执行它认为的优化,所以在每种情况下都会访问vtable它必须这样做。

就像你在情况1中说的一样,vtable访问没有关于对象是在堆中还是在堆栈中分配。这些是完全不同的概念。