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

  • 每个定义了虚函数的类都为其创建了一个 vtable
  • 创建类的实例时,将创建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将被调用。

    实际输出:
    C12克

    我想知道以下几件事:
  • C12的内存布局以及I1,I2指向
  • C12g如何作为输出打印。
  • 在两个不相关的接口(interface)之间进行类型转换时会发生什么?
  • 最佳答案

    您首先必须在这里了解的是,创建的实际对象*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偏移量(派生类已正确“ slice ”)。 ,但是I2*转换为I1*的C样式不会改变任何内容,因此编译器“认为”它指向I1,但它仍指向I2的实际C12 slice -而且“看起来”就像真实的I1类。

    家庭作业

    您可能会发现有趣的(并且可能与我已经描述的内存布局不一致)是在main()的末尾添加以下代码:
    C12* properC12 = new C12();// Points to the 'origin' of the class
    I1* properI1 = properC12; // Should (?) have same value as above?
    I2* properI2 = properC12; // Should (?) have an offset to 'slice'
    I1* dodgyI1 = (I1*)properC12; // Will (?) have same value as properI2!
    cout << std::hex << properC12 << endl;
    cout << std::hex << properI1 << endl;
    cout << std::hex << properI2 << endl;
    cout << std::hex << dodgyI1 << endl;
    

    请-尝试的任何人-请让我们知道值是什么,以及您使用的平台/编译器。在Visual Studio 2019中,针对x64平台进行编译,我得到了这些指针值:
    000002688A9726E0
    000002688A9726E0
    000002688A9726E8
    000002688A9726E0
    

    ...(某种程度上)与我所描述的内存布局(除了将v表放在其他地方而不是“块内”一样)一致。

    07-27 21:17