3

这与之前的许多问题相似,但它提出了一些我无法找到答案的问题。

#include <iostream>
using namespace std;

class Base1 {
    public:
        int b1_data;
        virtual void b1_fn() {cout << "I am b1\n";}
};
class Base2 {
    public:
        int b2_data;
        virtual void b2_fn() {cout << "I am b2\n";}
};
class Derived : public Base1, public Base2 {
    public:
        int d_data;
        void b1_fn() {cout << "I am b1 of d\n";}
        void b2_fn() {cout << "I am b2 of d\n";}
};

int main() {
    Derived *d = new Derived();
    Base1 *b1 = d;
    /*My observation mentioned below is implementation dependant, for learning,
    I assume, there is vtable for each class containing virtual function and in
    case of multiple inheritance, there are multiple vtables based on number of
    base classes(hence that many vptr in derived object mem layout)*/

    b1->b1_fn(); // invokes b1_fn of Derived because d points to 
                 // start of d's memory layout and hence finds vtpr to
                 // Derived vtable for Base1(this is understood)
    Base2 *b2 = d;
    b2->b2_fn(); // invokes b2_fn of Derived but how? I know that it "somehow" 
                 // gets the offset added to d to point to corresponding Base2 
                 // type layout(which has vptr pointing to Derived vtable for 
                 // Base2) present in d's memory layout. 
    return 0;
}

具体来说,b2 如何指向 vptr for vtable of Derived for Base2 以获取 b2_fn()?我试过从 gcc 看到 memlayout 转储,但想不通。

4

1 回答 1

3

编译器在多重继承的情况下,构造他的 vtable 以便每个子对象都有一个适当的 vtable。当然,这取决于实现(作为 vtables 本身),但它会像这样组织:

  • 一个Base1对象有一个 vptr 指向一个包含唯一指针的 vtableBase1::b1_fn
  • 一个Base2对象有一个 vptr 指向一个包含唯一指针的 vtableBase2::b2_fn
  • 一个Derived对象有一个 vptr 指向一个 vtable,该 vtable 以对应于的 vtable布局Base1开始,但使用Base2's vtable 的缺失元素对其进行扩展。对于“布局”,我的意思是指针b1_fn()的偏移量相同,但它可能指向一个覆盖函数。因此,在这里,表格将包含Derived::b1_fn后跟Derived::b2_fn. 这种组合布局确保Base1子对象Derived可以与其子对象共享 vtable。
  • 但是一个Derived对象是由 2 个子对象组成的:所以Base1子对象后面会跟着一个Base2子对象,它有自己的 vtable,使用 所需的布局Base2,但还是用Base2::b2_fn代替原来的。

Derived指针转换为指针时,编译器将通过应用在编译时确定的固定偏移量, Base2使其指向具有 vtable 的子对象。Base2

顺便说一句,如果您要进行向下转换,编译器将类似地使用另一个方向上的固定偏移量来查找Derived. 这一切都非常简单,直到您使用虚拟基础,其中固定偏移的技术不再适用。然后必须使用虚拟基指针,如this other SO answer中所述

这篇Dobb 博士的文章是对所有这些布局的最佳解释,并附有一些精美的图片。

于 2016-03-18T18:22:57.983 回答