了解从多个类派生的虚函数

问题描述 投票:3回答:1

我已经开始理解C++中的虚函数的工作,并且遇到了以下代码。这是我对虚函数的理解:

  1. 每个定义了虚函数的类,都有为其创建的vtable
  2. 创建类的实例时,将创建vptr,它指向该类的vtable

基于我的理解,我正在尝试分析以下代码的输出,而我无法解释代码如何显示“ C12g”。

class I1 {
  public: 
    virtual void f(){cout << "I1" << endl;}
};
class I2 {
  public: 
    virtual void g(){cout << "I2" << endl;}
};
class C12 : public I1, public I2 {
  public:
    virtual void f(){cout << "C12f" << endl;}
    virtual void g(){cout << "C12g" << endl;}
};
int main(int argc, char *argv[]) {
  I2 *o = new C12();
  ((I1*)o)->f();
}

我认为,由于C12对象被分配为I2类型,所以对象o只能访问其在g()中的方法C12(因为g被覆盖)。现在,由于o被强制转换为I1,所以我认为f()中的C12将被调用。

实际输出:C12g

我想了解以下内容:

  1. C12的内存布局以及I1,I2指向的内容
  2. C12g如何作为输出打印。
  3. 当在两个不相关的接口之间进行类型转换时会发生什么?
c++ virtual-functions
1个回答
4
投票

您首先必须了解的是创建的实际对象*o的类型为C12-因为这是您使用new C12()构造的。

接下来,使用虚函数,然后将调用实际对象的成员,无论您将指针转换为哪种“类型”。因此,当您在I2中强制转换为I2 *o = new C12()指针时,例如,如果您随后调用o-> g(),则对基础对象无关紧要,因为该对象会“知道”调用其覆盖的功能。

但是,当您将指针投射到“无关” I1*时,您会陷入奇怪的境地。请记住,类I1I2本质上具有相同的内存布局,然后在其中一个中调用f()将指向与在另一个中调用g()相同的“偏移”。但是,由于o实际上是指向I2的指针,因此调用最终到达的v表条目是I2中g的偏移量-被C12覆盖。

同样值得注意的是,您使用了C样式的强制转换来将I2*转换为I1*(但您也可以使用reinterpret_cast)。这很重要,因为这两个绝对不做任何事情

都指向指针或指向的对象/内存。

可能听起来有点乱,但我希望它能提供一些见识!

这里是一个可能

内存布局/场景-但这将是特定于实现的,并且在C样式强制转换之后使用类指针很可能构成未定义的行为!

可能的内存映射(简化,假设所有组件为4字节):
class I1:
0x0000: (non-virtual data for class I1)
0x0004: v-table entry for function "f"

class I2:
0x0000: (non-virtual data for class I2)
0x0004: v-table entry for function "g"

class C12:
0x0000: (non-virtual data for class I1)
0x0004: v-table entry for function "f"
0x0008: (non-virtual data for class I2)
0x000C: v-table entry for function "g"
0x0010: (class-specific stuff for C12)

现在,当您在C12*中进行从I2*I2 *o = new C12();的转换时,编译器理解了这两个类之间的关系,因此o将指向C12中的0x0008偏移量(派生类已正确“切片”)。 但是I2*转换为I1*的C样式没有任何改变,因此编译器“认为”它指向I1,但仍指向实际的I2 ] C12的一部分-这看起来像真正的I1类。

© www.soinside.com 2019 - 2024. All rights reserved.