有这样的代码:
#include <iostream> class Base { public: Base() { std::cout << "Base: " << this << std::endl; } int x; int y; int z; }; class Derived : Base { public: Derived() { std::cout << "Derived: " << this << std::endl; } void fun(){} }; int main() { Derived d; return 0; }
输出:
Base: 0xbfdb81d4 Derived: 0xbfdb81d4
但是当派生类中的功能“fun”更改为虚拟时:
virtual void fun(){} // changed in Derived
那么在这两个构造函数中的“this”的地址是不一样的:
Base: 0xbf93d6a4 Derived: 0xbf93d6a0
另一件事是如果类Base是多态的,例如我添加了一些其他的虚函数:
virtual void funOther(){} // added to Base
然后再次地址“this”匹配:
Base: 0xbfcceda0 Derived: 0xbfcceda0
问题是 – 为什么在Base类和Derived类不同时,Base类和Derived类中的’this’地址是不同的?
解决方法
当您具有类的多态单继承层次结构时,大多数(如果不是全部)编译器遵循的典型约定是该层次结构中的每个对象必须以VMT指针(指向虚方法表的指针)开始.在这种情况下,VMT指针早期被引入到对象存储器布局中:通过多态层次结构的根类,而所有较低级别只是继承它并将其设置为指向其正确的VMT.在这种情况下,任何派生对象中的所有嵌套子对象都具有相同的值.这样通过读取*的内存位置,编译器可以立即访问VMT指针,而不考虑实际的子对象类型.这正是您最后一次实验中发生的情况.当您创建根类多态时,所有这些值都匹配.
但是,当层次结构中的基类不是多态时,它不会引入VMT指针. VMT指针将由层次结构中较低层的第一个多态类引入.在这种情况下,普遍的实现方法是在由层次结构的非多态(上)部分引入的数据之前插入VMT指针.这是你在第二个实验中看到的. Derived的内存布局如下所示
+------------------------------------+ <---- `this` value for `Derived` and below | VMT pointer introduced by Derived | +------------------------------------+ <---- `this` value for `Base` and above | Base data | +------------------------------------+ | Derived data | +------------------------------------+
同时,层次结构的非多态(上)部分中的所有类都不应该知道任何VMT指针. Base类型的对象必须以数据字段Base :: x开头.同时层次结构的多态(下)部分中的所有类必须以VMT指针开头.为了满足这两个要求,编译器被强制调整对象指针值,因为它将层次结构从一个嵌套的子对象转换为另一个.这意味着跨多态/非多态边界的指针转换不再是概念性的:编译器必须添加或减少一些偏移量.
来自层次结构的非多态部分的子对象将共享它们的值,而来自层次结构的多态部分的子对象将共享它们自己的不同的该值.
在沿着层次结构转换指针值时不得不添加或减少一些偏移量是不寻常的:编译器必须在处理多重继承层次结构时一直执行此操作.但是,您的示例显示了如何在单继承层次结构中实现它.
Derived *pd = new Derived; Base *pb = pd; // Numerical values of `pb` and `pd` are different if `Base` is non-polymorphic // and `Derived` is polymorphic Derived *pd2 = static_cast<Derived *>(pb); // Numerical values of `pd` and `pd2` are the same