C++深入学习-对象模型

对象内存模型的必要

    当提起int,short,long,double和float等类型时,我们已经牢记了这些类型占用内存大小,占用几个字节,表示范围是多少,掌握了这些信息是编写程序最基本的要求,而且我们确实也需要知道在那种场景下用哪种类型。但是对于自定义的类型,我们很少去考虑它的内存占用空间和内存结构模型,有同学说:我不知道对象的内存模型,照样能把程序写的很好。但愿事情是这样的吧。但是我相信,知己知彼,才会百战不殆。

空的类

class CEmpty{
};

    空的类对象大小不是0,是一个字节,即用一个char来表明自己的存在。

包含静态变量的类

class CAnimal {
public:
    CAnimal();
    ~CAnimal();
private:
    int m_member;
    static int m_stcMem;
};

    类的静态变量属于类的特性,不属于某一个对象。所以,在对象的模型中是找不到m_stcMem的,因此CAnimal 对象的大小为4字节,模型示意如下:

class CAnimal   size(4):
        +---
 0      | m_member
        +---

支持多态的类

    多态是C++的基本特性之一,我们这里只讨论与对象模型相关的动态属性的多态(静态属性的多态为:函数重载和函数模板)深入理解对象的模型构造才能理解这一特性实现的原理。

class CAnimal {
public:
    CAnimal() {}
    ~CAnimal() {}
public:
    virtual void Run() = 0;
private:
    int m_member;
};

    类的多态是由virtual函数来实现的,更确切地说是由virtual机制来实现的,当类的函数具有这种vitual特性以后,所对应的对象(虽然这个CAnimal不能实例化,当然不能实例化,你见过一个动物叫‘Animal’吗)结构以及大小是怎样的呢?看下图:

//CAnimal Object
class CAnimal   size(8):
        +---
 0      | {vfptr}
 4      | m_member
        +---
//Virtual function table  
CAnimal::$vftable@:
        | &CAnimal_meta
        |  0
 0      | &CAnimal::Run

    可以看到:CAnimal的对象大小变成了8字节:一个成员变量m_member(4字节),一个vfptr指针(4字节:一个指针,不管它指向哪一种数据类型,指针本身所占用的内存是固定的)。vfptr指针指向了一个CAnimal::$vftable@ 称之为virtual function table(虚函数表)这个表存储了CAnimal内所有virtual的函数,这个表既然不占用对象的空间,它存储在哪里?
    由上面对象模型可以看到,生成类对象的时候,编译器会将类对象的前四个字节设置为虚表的地址,而这四个字节就是一个指向虚函数表的指针,我们可以通过这个地址找到虚函数表的位置。由此看来虚函数表属于类,类的所有对象共享这个类的虚函数表。

    基类的对象结构就是这样。但是,派生子类的对象内部是什么样的结构呢

class CAnimal {
public:
    CAnimal() {}
    ~CAnimal() {}
public:
    virtual void Run() = 0;
    virtual void Eat() = 0;
private:
    int m_member;
};
class CDog : public CAnimal {
    CDog() {}
    ~CDog() {}
public:
    void Run() {}
    void Eat() {}

    virtual void Sleep() {}
private:
    int m_dogMember;
};

    首先可以肯定的是,子类对象中肯定有父类的特征,子类所具有的自己“独创”的virtual函数,应该也会加入到虚函数表中,以此支持自己后代的多态性。如下图:

//CDog Object
class CDog      size(12):
        +---
 0      | +--- (base class CAnimal)
 0      | | {vfptr}
 4      | | m_member
        | +---
 8      | m_dogMember
        +---
//Virtual function table 
CDog::$vftable@:
        | &CDog_meta
        |  0
 0      | &CDog::Run
 1      | &CDog::Eat
 2      | &CDog::Sleep

    类的继承体系当中还有一种情形是:虚继承。这里的“虚”,其实理解为"共享"更好一些,就是在对象模型当中,虚继承的基类被作为共享目标而存在。下面是简单的代码:

class CAnimal {
public:
    CAnimal() {}
    ~CAnimal() {}
public:
    virtual void Run() = 0;
    virtual void Eat() = 0;
private:
    int m_member;
};
class CWolf : virtual public CAnimal {
public:
    CWolf() {}
    ~CWolf() {}
public:
    virtual void Run(){}
    virtual void Eat(){}
};

发生virtual派生之后,对象模型则是:

//CWolf Object
class CWolf     size(16):
        +---
 0      | {vbptr}
        +---
4       | (vtordisp for vbase CAnimal)
        +--- (virtual base CAnimal)
 8      | {vfptr}
12      | m_member
        +---
//Virtual base table        
CWolf::$vbtable@:
 0      | 0
 1      | 8 (CWolfd(CWolf+0)CAnimal)
//Virtual function table
CWolf::$vftable@:
        | -8
 0      | &(vtordisp) CWolf::Run
 1      | &(vtordisp) CWolf::Eat

    可以比较一下,虚继承发生时,对象中多了一个vbptr,注意是vbptr(虚基类指针),不是之前说到的vfptr(虚函数表指针)。前面我们说了,virtual继承可以理解为“共享”,"共享"的是什么呢,当然是Base 类 。我们可以从下面这些代码看到这样设计的原因。.

class CAnimal {
public:
    CAnimal() {}
    ~CAnimal() {}
public:
    virtual void Run() = 0;
    virtual void Eat() = 0;
private:
    int m_member;
};
class CWolf : virtual public CAnimal {
public:
    CWolf() {}
    ~CWolf() {}
public:
    virtual void Run(){}
    virtual void Eat(){}
    virtual void WolfEat() {}
private:
    int m_wolf;
};
class CDog : virtual public  CAnimal {
public:
    CDog() {}
    ~CDog() {}
public:
    virtual void Run() {}
    virtual void Eat() {}
    virtual void DogEat() {}
private:
    int m_dog;
};
class CWolfDog : public  CWolf, public CDog{
public:
    CWolfDog() {}
    ~CWolfDog() {}
public:
    virtual void Run() {}
    virtual void Eat() {}
    void WolfDogEat() {}
};

    简单来说CWolfDog 类的两个父类CWolf 和CDog有一个共同的父类CAnimal ,这个时候我们来看CWolfDog对象中CWolf 和CDog对应的结构是怎么表示他们共同的Parent。对象模型如下:

//CWolfDog Object
class CWolfDog  size(36):
        +---
 0      | +--- (base class CWolf)
 0      | | {vfptr}
 4      | | {vbptr}
 8      | | m_wolf
        | +---
12      | +--- (base class CDog)
12      | | {vfptr}
16      | | {vbptr}
20      | | m_dog
        | +---
        +---
24      | (vtordisp for vbase CAnimal)
        +--- (virtual base CAnimal)
28      | {vfptr}
32      | m_member
        +---
//Virtual function table
CWolfDog::$vftable@CWolf@:
        | &CWolfDog_meta
        |  0
 0      | &CWolf::WolfEat
CWolfDog::$vftable@CDog@:
        | -12
 0      | &CDog::DogEat
//Virtual Base table
CWolfDog::$vbtable@CWolf@:
 0      | -4
 1      | 24 (CWolfDogd(CWolf+4)CAnimal)
CWolfDog::$vbtable@CDog@:
 0      | -4
 1      | 12 (CWolfDogd(CDog+4)CAnimal)
//Virtual function table
CWolfDog::$vftable@CAnimal@:
        | -28
 0      | &(vtordisp) CWolfDog::Run
 1      | &(vtordisp) CWolfDog::Eat

    确实,在CWolfDog对象中只存在一个CAnimal的结构,但是,它的名称变成了virtual base CAnimal,我的理解是:这是在标明这是一个共享的CAnimal结构。(我们还注意到这时的CAnimal前面还有一个修饰语vtordisp,对此MSDN给出的解释是:虚继承中派生类重写了基类的虚函数,并且在构造函数或者析构函数中使用指向基类的指针调用了该函数,编译器会为虚基类添加vtordisp域先忽略吧) 而CWolf和CDog的基类都用一个vbptr,这个指针指向了共享的CAnimal结构,以此来避免了对象调用时的选择恐惧症(恐怖菱形二义性Ambiguous)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值