无法理解程序的输出

问题描述 投票:1回答: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在C12中只能访问其方法g()(因为g被覆盖)。现在,因为o被强制转换为I1类型,所以我认为C12中的f()将被调用。

实际输出:C12g

我想了解以下内容1)C12的内存布局以及I1,I2指向的内容2)C12g如何作为输出打印。3)在两个不相关的接口(这里是I2至I1)之间进行类型转换时,会发生什么?

c++ virtual-functions
1个回答
2
投票

您首先必须了解的是创建的实际对象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) 0x0000: 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偏移量(派生类已被

sliced

)。 但是
I2*转换为I1*的C样式没有任何改变,因此编译器“认为”它指向I1,但仍指向实际的I2 ]切片的C12-看起来就像是真正的I1类。
© www.soinside.com 2019 - 2024. All rights reserved.