首页 > 解决方案 > 如何在类中的其他 vtable 指针中选择 vtable 指针?

问题描述

我很感兴趣virtual inheritance,这件事给我带来了神秘感。让我们考虑一个例子virtual inheritance

struct Base {
    virtual void v() { std::cout << "v"; }
};

struct IntermediateDerivedFirst : virtual Base {
    virtual void w() { std::cout << "w"; }
};

struct IntermediateDerivedSecond : virtual Base {
    virtual void x() { std::cout << "x"; }
};

struct Derived : IntermediateDerivedFirst, IntermediateDerivedSecond {
    virtual void y() { std::cout << "y"; }
};

最后,Derived应该是这样的:

 --------
|[vtable]| -----> [ vbase offset 20               ]
|[vtable]|---     [ top offset    0               ]
|[vtable]|- |     [ Derived typeinfo              ]
 --------  ||     [ IntermediateDerivedFirst::w() ]
           ||     [ Derived::y()                  ]
           ||
           |----> [ vbase offset 12               ]
           |      [ top offset   -8               ]
           |      [ Derived typeinfo              ]
           |      [ IntermediateDerivedSecond::x()]
           |
           -----> [ vbase offset 0               ]
                  [ top offset   -20             ]
                  [ Derived typeinfo             ]
                  [ Base::v()                    ]

因此,从字面上看,virtual继承将vtable最基类移动到最后,正如我们所见vtable——s for IntermediateDerivedFirstIntermediateDerivedSecond不包含方法Base的地址v()。好的,那么,我们可以看到这个类有几个vtables。让我们考虑一个代码:

IntermediateDerivedFirst* fb = new Derived;
fb->v();
delete fb;

然而,这个调用仍然有效,vtable因为IntermediateDerivedFirst没有关于v()方法的信息,而且它似乎在这里使用了一些魔法,它使用第三个vtable指针来调用v()。那么,编译器如何选择需要的vtable指针来获取被调用函数的地址呢?

标签: c++inheritancevirtual

解决方案


Bjarne Stroustroup 写了一篇关于使用 C++ 解决多重继承中的“钻石问题”的详细论文: Stroustrup, B Fall 1989, 'Multiple Inheritance for C++'。计算系统,卷。2 第 4 期,第 367-395

在两个 IntermediateDerived 类中声明了“虚拟基础”;保证您只获得公共基类的单个实例。

来自C++ 语言/类/派生类

对于指定为 virtual 的每个不同的基类,最派生的对象仅包含该类型的一个基类子对象,即使该类在继承层次结构中出现多次(只要它每次都是继承 virtual)。

也就是说,编译器将为每个提供一个实例:

  • 衍生的
  • 中间派生优先
  • 中间派生次
  • 根据

两个 IntermediateDerived_X 类在它们的 vtable 中都有一个虚拟指针,用于存储 Base 类的偏移量。当任何一个 IntermediateDerived_X 类尝试访问 Base::v() 时,它使用它的 vtable 中的虚拟指针来查找 Base 对象。沿着这条线;如果 Derived 继承了 100 个以上具有相同“虚拟基础”的 IntermediateDerived_X 类,则仍然只有一个 Base 实例。对 Base::v() 函数的调用使用虚拟指针来访问 Base 类的实例。

重要的是要注意 Base 对象只被构造一次;当它在 Derived 类中首次初始化时。继承的类将从 Base 构造到大多数派生类。反过来; 在示例实例化中,销毁的顺序将从大多数派生到 Base;调用“new Derived”将构造四个类中的每一个的单个实例。

根据上面的地址表:IntermediateDerivedFirst 知道'vbase offset = 20',这是Base object vtable 的位置。IntermediateDerivedSecond 可以以相同的方式访问 Base 类(vbase offset = 12);并会产生完全相同的功能。 没有神奇的第三个 vtable,而只是一个指向在所有派生类之间共享的 Base 的 vtable 的指针。

从实施的角度来看;在所有虚拟类中添加一个指向基类的单个指针通常比将虚拟表复制到每个派生类更节省内存。不同的编译器、优化器、链接器等可能会稍微不同地实现或更改生成的 vtable。但是所有使用虚拟继承的派生类都只会指向一个 Base 对象。


推荐阅读