【问题标题】:How vtable pointer is chosen among other vtable pointers in a class?如何在类中的其他 vtable 指针中选择 vtable 指针?
【发布时间】:2021-07-18 17:52:59
【问题描述】:

我对@9​​87654321@ 很感兴趣,这件事给我带来了神秘感。让我们以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 移动到最后,正如我们所见——vtables 用于IntermediateDerivedFirstIntermediateDerivedSecond 不包含Base 的地址的v() 方法。好的,那么,我们可以看到该类很少有vtables。 让我们考虑一个代码:

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

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

【问题讨论】:

  • 这是一个实现细节。一种可能性是虚函数调用包含一个偏移量,以便“this”正确指向 Base 的一个副本(或任何实现虚函数的类)。
  • 您可能对 Lippman 的“Inside the C++ Object Model”一书感兴趣。它深入探讨了此类实现细节。
  • 是什么让你相信Derived的布局和你描述的一样?你是怎么确定的?这对我来说似乎不太合理——通常,最派生类的 vtable 包含指向所有基类的虚函数的指针。所以我完全期望IntermediateDerivedFirst实例的vtable包含一个指向Base::v实现的指针。
  • 顺便说一下,delete fb; 在您的示例中表现出未定义的行为,通过指向其基类的指针删除没有虚拟析构函数的类的实例。
  • 抱歉,提供赏金并不会改变 vtables 的布局(或者甚至,理论上,vtables 的存在,因为虽然是一种常见的实现方法,但标准不需要 vtables)是高度特定于编译器。这意味着您提出的问题没有规范的答案。 C++ 标准根本不需要您所说的“Derived 应该看起来像”。如果您正在寻找特定于特定编译器的答案,那么您需要识别该编译器,并相应地关注问题/标签。

标签: c++ inheritance virtual


【解决方案1】:

Bjarne Stroustroup 写了一篇关于使用 C++ 解决多重继承中的“钻石问题”的详细论文: Stroustrup, B Fall 1989, 'Multiple Inheritance for C++'. Computing Systems, Vol. 2 No. 4, p. 367-395

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

来自C++ Language / Classes / Derived classes

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

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

  • 派生
  • IntermediateDerivedFirst
  • IntermediateDerivedSecond
  • 基础

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-03
    • 2013-11-04
    • 1970-01-01
    • 1970-01-01
    • 2010-10-08
    • 2012-09-21
    • 2011-01-05
    • 2011-01-03
    相关资源
    最近更新 更多