我已经开始了解虚拟函数在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)之间进行类型转换时,会发生什么?
您首先必须了解的是创建的实际对象,o
的类型为C12
-因为这是您使用new C12()
构造的。
接下来,使用虚函数,然后将调用实际对象的成员,无论您将指针转换为哪种“类型”。因此,当您在I2
中强制转换为I2 *o = new C12()
指针时,例如,如果您随后调用o-> g(),则对基础对象无关紧要,因为该对象会“知道”调用其覆盖的功能。
但是,当您将指针投射到“无关” I1*
时,您会陷入奇怪的境地。请记住,类I1
和I2
本质上具有相同的内存布局,然后在其中一个中调用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
类。