首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >基类不是多态的,但派生的是

基类不是多态的,但派生的是
EN

Stack Overflow用户
提问于 2012-07-22 00:12:11
回答 3查看 2K关注 0票数 9

下面是这段代码:

代码语言:javascript
复制
#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;
}

输出:

代码语言:javascript
复制
Base: 0xbfdb81d4
Derived: 0xbfdb81d4

但是,当在派生类中将函数“fun”更改为virtual时:

代码语言:javascript
复制
virtual void fun(){} // changed in Derived

那么在两个构造函数中'this‘的地址是不同的:

代码语言:javascript
复制
Base: 0xbf93d6a4
Derived: 0xbf93d6a0

另一件事是如果基类是多态的,例如我在那里添加了一些其他的虚函数:

代码语言:javascript
复制
virtual void funOther(){} // added to Base

那么两个'this‘的地址再次匹配:

代码语言:javascript
复制
Base: 0xbfcceda0
Derived: 0xbfcceda0

问题是-当基类不是多态的而派生类是多态的时候,为什么'this‘地址在基类和派生类中是不同的?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-07-22 00:25:22

当您拥有多态的单继承类层次结构时,大多数(如果不是全部)编译器遵循的典型约定是,该层次结构中的每个对象都必须以VMT指针(指向虚拟方法表的指针)开头。在这种情况下,VMT指针被提前引入到对象内存布局中:通过多态层次结构的根类,而所有较低的类只是简单地继承它并将其设置为指向其适当的VMT。在这种情况下,任何派生对象中的所有嵌套子对象都具有相同的this值。这样,通过读取*this中的内存位置,编译器可以立即访问VMT指针,而不管实际的子对象类型是什么。这正是你上一个实验中发生的事情。当您使根类成为多态时,所有的this值都匹配。

但是,当层次结构中的基类不是多态的时,它不会引入VMT指针。VMT指针将由层次结构中较低位置的第一个多态类引入。在这种情况下,一种流行的实现方法是在层次结构的非多态(上部)部分引入的数据之前插入VMT指针。这就是你在第二个实验中看到的。Derived的内存布局如下所示

代码语言:javascript
复制
+------------------------------------+ <---- `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指针开头。为了满足这两个要求,当对象指针值在层次结构中从一个嵌套基子对象向上和向下转换时,编译器必须调整对象指针值。这立即意味着跨越多态/非多态边界的指针转换不再是概念性的:编译器必须增加或减少一些偏移量。

来自层次结构的非多态部分的子对象将共享它们的this值,而来自层次结构的多态部分的子对象将共享它们自己的不同的this值。

在沿着层次结构转换指针值时,必须添加或减去一些偏移量是很常见的:在处理多重继承层次结构时,编译器必须一直这样做。但是,您的示例展示了如何在单继承层次结构中实现它。

加法/减法效果也将在指针转换中显示出来

代码语言:javascript
复制
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
票数 14
EN

Stack Overflow用户

发布于 2012-07-22 00:35:52

这看起来像是在对象中使用v-table指针的多态性的典型实现的行为。Base类不需要这样的指针,因为它没有任何虚方法。这在32位机器上节省了4个字节的对象大小。典型的布局是:

代码语言:javascript
复制
+------+------+------+
|   x  |   y  |   z  |
+------+------+------+

    ^
    | this

然而,派生类确实需要v-table指针。通常存储在对象布局中的偏移量0处。

代码语言:javascript
复制
+------+------+------+------+
| vptr |   x  |   y  |   z  |
+------+------+------+------+

    ^
    | this

因此,为了使基类方法看到对象的相同布局,代码生成器在调用基类的方法之前向this指针添加4。构造函数看到:

代码语言:javascript
复制
+------+------+------+------+
| vptr |   x  |   y  |   z  |
+------+------+------+------+
           ^
           | this

这就解释了为什么在Base构造函数中的this指针值中添加了4。

票数 6
EN

Stack Overflow用户

发布于 2012-07-22 01:24:59

从技术上讲,this就是所发生的事情。

然而,必须注意的是,根据语言规范,多态性的实现不一定与vtable相关:这就是规范。定义为“实现细节”,这超出了规范的范围。

我们所能说的就是this有一个类型,并指向通过它的类型可以访问的内容。取消对成员的引用是如何发生的,同样是一个实现细节。

事实上,当通过隐式、静态或动态转换将pointer to something转换为pointer to something else时,必须进行更改以适应周围的情况,这一事实必须被视为规则,而不是例外。

按照定义C++的方式,这个问题和答案一样没有意义,因为它们隐含地假设实现是基于假定的布局。

事实上,在给定的情况下,两个对象子组件共享同一原点,这只是一个(非常常见的)特殊情况。

例外是“重新解释”:当你“看不见”类型系统,只说“看这一堆字节,因为它们是这个类型的一个实例”:这是唯一的一种情况,你不需要期待地址改变(编译器也不会对这种转换的意义负责)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11593783

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档