2013-05-08 134 views
2

考虑下面的代码:虚表的存储位置

namespace Example1 { 

class Base1 { 
public: 
    Base1() {} 
    virtual ~Base1() {} 
protected: 
    float data_Base1; 
}; 

class Base2 { 
public: 
    Base2() {} 
    virtual ~Base2() {} 
protected: 
    float data_Base2; 
}; 

class Derived : public Base1, public Base2 { 
public: 
    Derived() {} 
    virtual ~Derived() {} 
protected: 
    float data_Derived; 
}; 

class Derived2 : public Base1 { 
public: 
    Derived2() {} 
    virtual ~Derived2() {} 
protected: 
    float data_Derived2; 
}; 

} 

int main (void) 
{ 
using namespace Example1; 

Base2* pbase2 = new Derived;   
Base1* b = new Base1(); 
     Base1* b2 = new Base1(); 
Derived* d = new Derived; 
Derived* d2= new Derived; 

Derived2* dd = new Derived2; 
} 

与Visual Studio 2012的编译器,似乎在多重继承中,派生类含有n-1个额外的虚拟表。而这正是Derived类所发生的。

但它似乎也与Derived2的(只从基础1类继承)发生

这里的DD内存映射:

Example1::Base1 
    __vfptr 
    [0]  0x00c4127b 

这里的B内存映射:

__vfptr 
    [0]  0x00c411ae 

正如你可以看到,第一个虚拟表格插槽的地址是不同的。 例如b和b2具有相同的虚拟表格。

好了,所以现在对于两个问题:

1)为什么他们不共享相同的虚拟基础1表? (Derived2和Base1对象)

2)为什么派生类甚至需要持有n-1个虚拟表? (当N表示派生类继承的类的数量)

谢谢!

+1

btw。这些是实现细节,可能在实现方面有所不同,最好问的可能是制造商,我们在这里大多可能猜到 – PlasmaHH 2013-05-08 09:34:09

回答

0

首先,Derived2Base1之外的另一种类型,所以除虚拟函数表外,还需要一些其他信息。其次,至少Derived2的析构函数是Base1之外的另一个函数,所以即使表中只有虚函数,该条目也不相同。 我不确定MSVC如何在多态类型上实现RTTI,但是必须有一些与虚函数不同的类型的标识,启用dynamic_cast s。所以第一个条目很可能是指向RTTI的指针。我没有MSVC周围的时刻,但你可以试试这个:

struct Base { 
    virtual void foo() {}; 
    virtual void bar() {}; 
    virtual ~Base(); 
}; 

struct Derived { 
    virtual void foo() {}; 
    virtual ~Derived(); 
}; 

int main() { 
    Base* b1 = new Base; 
    Base* b2 = new Derived; 
}; 

现在检查两个创建的对象的__vfptr的前四个或五个要素,我的猜测是,你会看到一个条目是一样的 - 这是指向Base::bar。其他(RTTI指针,foo和析构函数)应该是不同的。
这里有一些gueswork:也许你可以看到指针指向的内存中的不同区域,因为RTTI指针可能指向数据段,而虚拟函数指针指向代码段。

更新:不必存在的虚函数表本身RTTI的条目 - 这可能是可能是一些编译器实现RTTI只是比较的虚函数表的地址。

0

每个班级都有它自己的vtable。在这种情况下,每个类都有一个独特的虚拟析构函数,所以这本身就意味着vtable需要不同。如果要构建一个没有任何不同虚函数的类,编译器可能会决定“重用”相同的虚拟表。但不能保证。

如果一个类来自多个类,那么每个具有任何类型虚函数的类都需要有一个vtable。这样,如果类被转换(通过使用指向基类的指针或dynamic_cast)到其中一个基类中,则可以调用这两个基类的虚函数。

另请注意:编译器如何处理vtables完全取决于编译器,并且不保证它们的工作方式的任何方面。

+1

如果编译器使用vtable for RTTI(大多数/所有编译器都这样做),它们将不得不不同对于不同的类,即使它们的所有虚拟函数都是相同的,也就是说没有虚拟的dtor,也没有虚拟函数被重写或者被派生类中添加。 – 2013-05-08 09:44:33