声明指向unsigned int类型的对象的指针vptr_《深度探索C++对象模型》阅读笔记-第1章...

杨文的博客​oven-yang.github.io

对象模型

  • 简单对象模型(A Simple Object Model)

对象保存成员的指针,成员实际存放在其他区域.

这种模型保证了成员中每个元素需要的内存空间都是一样的, 不论元素是何种类型, 访问时只需要确定成员的index和对象首地址即可. 坏处是访问成员时需要多进行一次内存访问, 并且占用了更多的内存空间.

  • 表格驱动对象模型(A Table-driven Object Model)

将对象中的数据成员和函数成员分别组织, 放在不同的内存区域, 对象中保存了指向这两个内存区域的指针.

这种模型保证了不同类型的对象具有相同的内存布局,

C++对象模型

每个有虚函数的类产生一个virtual table, 保存所有虚函数的指针, 对象在所在内存开始位置保存指向 vtable 的指针 vptr. 在调用类的虚函数时, 会沿着虚函数表搜索, 第一个满足条件的就是要调用的函数. 在调用类的构造函数, 复制构造函数等时这些函数会自动处理类的vptr. 比如下面的代码中, 假设类Base 是Derived的父类, 二者都定义了函数f(), 那么自然就有vptr. 在第二行中, 调用了Base的复制构造函数, 对象d会被截断, 并且b的vptr会在复制构造函数中设置, 因此第三行中b调用的是Base中的f(). 另外一个值得注意的是, 运行时多态只能通过指针和引用来实现.

Derived d;
Base b = b;
b.f();

单继承且无继承时, 每个对象只有一个vptr, 而当存在虚继承时, 虚继承的直接子类还会产生一个附加的 vptr, 指向自身的virtual table. 当存在多继承时, 会为每个父类产生一个vptr. 下面针对这些情况 详细举例说明.

  • 单继承, 无虚继承时的对象模型

这是最简单的情况, 在对象的开始处保存一个vptr指针, 指向一个虚函数指针数组, 非静态数据成员按继承, 声明的顺序排列.

  • 单继承, 有虚继承时的对象模型

采用虚继承的类会在产生多个vptr, 对象开始处是父类的vptr, 父类成员之后, 子类成员之前保存子类的vptr. 如下:

class BB
{
    int64_t m_bb;
};
class B1 : public virtual BB
{
    int64_t m_b1;
};
class DD : public B1
{
    int64_t m_dd;
};
class D1 : public DD
{
    int64_t m_d1;
};
// D1对象的结构, gcc 8.2.0, GNU gdb 8.1.1
// 注意, 这是在gdb中查看的结果, 并不代表真正的对象内存布局. 比如多继承, 有虚继承的情况.
{
    <DD> =
    {
        <B1> =
        {
            <BB> =
            {
                _vptr.BB = <vtable for D1+112>, // 父类的vptr
                m_bb
            },
            _vptr.B1 = <vtable for D1+24>, // 虚继承子类B1的vptr
            m_b1
        },
        m_dd
    },
    m_d1 // 没有采用虚继承, 因此与基类BB共用vptr.
}
  • 继承, 无虚继承时的对象模型

保留多个父类的vptr.

class BB
{
    int64_t m_bb;
};
class B1 : public BB
{
    int64_t m_b1;
};
class B2 : public BB
{
    int64_t m_b2;
};
class DD : public B1, B2
{
    int64_t m_dd;
};
// DD对象的结构, gcc 8.2.0, GNU gdb 8.1.1
{
    <B1> =
    {
        <BB> =
        {
            _vptr.BB = <vtable for DD+16>, // B1的vptr
            m_bb
        },
        m_b1
    },
    <B2> =
    {
        <BB> =
        {
            _vptr.BB = <vtable for DD+80>, // B2的vptr
            m_bb
        },
        m_b2
    },
    m_dd // 与基类B1共用vptr.
}

BB *bp = new DD; // 错误, 有歧义
BB *bp1 = dynamic_cast<B1*>(new DD); // 正确, bp1指向DD中的B1部分.
BB *bp2 = dynamic_cast<B2*>(new DD); // 正确, bp2指向DD中的B2部分.

很自然地, 当用BB类型的指针/引用保存DD对象时, 就会出现歧义, 编译器无法确定采用B1中的BB还是B2中BB. 可以使用 dynamic_cast 进行干预, 以达到预期目的.

  • 多继承, 有虚继承时的对象模型
    有了上面的结论, 就不难推测这种情况下的对象模型了.
class BB
{
    int64_t m_bb;
};
class B1 : virtual public BB
{
    int64_t m_b1;
};
class B2 : virtual public BB
{
    int64_t m_b2;
};
class DD : public B1, B2
{
    int64_t m_dd;
};
// DD对象的结构, gcc 8.2.0, GNU gdb 8.1.1
{
    <B1> =
    {
        <BB> =
        {
            _vptr.BB = <vtable for DD+160>,
            m_bb = 1
        },
        _vptr.B1 = <vtable for DD+24>,
        m_b1
    },
    <B2> =
    {
        _vptr.B2 = <vtable for DD+88>,
        m_b2
    },
    m_dd
}
// 实际内存布局可能是:
{
    <B1> =
    {
        vptr.B1,
        m_b1
    },
    <B2> =
    {
        vptr.B2,
        m_b2
    }
    m_dd,
    <BB> =
    {
        vptr.BB,
        m_bb
    }
}

关键字class和struct的区别

二者在绝大多数情况下是完全相同的, 可以互换, 只有几点不同.

class可以用于模板声明, struct不可以. C++引入class关键字, 保留struct的一个原因是为了体现OO, 并且兼容C, 而C中不需要模板, 也就不需要保证struct可以用于模板.

另外, 当用于声明类类型时二者略有差别:

  • 用class声明的类的成员的默认访问级别是private, 用struct声明的类的成员的默认访问级别是public.
  • 有继承时, 用class声明的类的默认继承方式是private, 用struct声明的类的默认继承方式是public. 这里的class, struct是指用于子类, 父类的声明方式不影响默认方式. 如下代码:
class BB {};
class D1 : BB {}; // private继承
struct D2 : BB {}; // public继承 

运行时多态必须通过public继承实现

这个设计是符合逻辑的. 可以设想, 如果使用其他继承方式, 那么从逻辑上说, 在类外不应该能访问父类成 员. 但是要实现运行时多态, 正常做法是将子类指针/引用赋值给一个父类类型的指针/引用(设为bp), 一旦复制成功, 我们就可以通过bp访问父类的public成员, 这显然与前面的逻辑要求矛盾. 所以, 在C++中, 前面说得"赋值"是违法的. 而没有这个"赋值"操作, 也就无法实现运行时多态, 因此必须通过public继承实现运行时多态.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值