C++对象模型:单继承,多继承,虚继承,菱形虚继承,及其内存布局图

C++目前使用的对象模型:

此模型下,nonstatic数据成员被置于每一个类的对象中,而static数据成员则被置于类对象之外static和nonstatic函数也都放在类对象之外(通过函数指针指向),而对于virtual函数,则通过虚函数表+虚函数指针来支持

1)每个类生成一个表格,称为虚表(virtual table,简称vtbl),虚函数表中存在一堆指针,这些指针指向该类的每一个虚函数,虚表中的函数地址按照声明时的顺序排列

2)每个类对象都有一个虚表指针(简称vptr),由编译器为其生成,虚表指针的设定和重置皆由类的相关函数控制(构造函数,析构函数,赋值操作符),虚表指针vptr的位置由编译器决定,一般编译器把vptr放在一个类对象的最前端(也就是说对象的地址就是vptr的地址)

3)虚函数表的前面设置了一个指向type-info的指针,用以支持RTTI(Run Time Type Identification,运行时类型识别),RTTI是为支持多态生成的信息,包括对象的继承关系,对象本身的描述等,只有具有虚函数的对象才会生成

样例:

class Base
{
public:
 
    Base(int i) :baseI(i){};
  
    int getI(){ return baseI; }
 
    static void countI(){};
 
    virtual void print(void){ cout << "Base::print()"; }

    virtual ~Base(){}
 
private:
 
    int baseI;
 
    static int baseS;
};

461913-20160806163200793-1806847633

 

单继承(父类含虚函数)

原则:

  1.子类和父类各自拥有一个虚函数表

  2.若子类并没有overwrite父类虚函数,那么子类就使用父类虚函数

  3.若子类overwrite了父类虚函数,则子类虚函数将覆盖虚子类虚函数表中对应的父类虚函数

  4.若子类声明了自己的虚函数,则该虚函数地址将扩充到虚函数表最后

样例:

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void fun1()
    {
        cout << "Base fun1" << endl;
    }
    virtual void fun2()
    {
        cout << "Base fun2" << endl;
    }
private:
    int a;
};

class Derive :  public Base
{
public:
    void fun2()
    {
        cout << "Derive fun2" << endl;
    }
    virtual void fun3() {}
private:
    int b;
};

int main()
{
    Base b;
    Derive d;
    Base *p = &d;
    p->fun1();
    p->fun2();

    return 0;
}
/*
程序运行结果:

Base fun1
Derive fun2

*/

QQ图片20190917171937

 

一般多继承(不考虑菱形继承的多继承,因为菱形继承需要用到虚继承,之后讨论)

原则:

  1.若子类新增虚函数,则放在声明的第一个父类的虚函数表中

  2.若子类重写了父类的虚函数,所有父类的虚函数表都要改变

  3.内存布局中,父类按照其声明顺序排列

#include <iostream>
using namespace std;

class Base1
{
public:
    virtual void fun1() {}
private:
    int m_base1;
};

class Base2
{
public:
    virtual void fun1() {}
    virtual void fun2() {}
private:
    int m_base2;
};

class Derive :  public Base1,public Base2
{
public:
    void fun1() {}
    virtual void fun3() {}
private:
    int m_derive;
};

int main()
{
    Base1 b1;
    Base2 b2;
    Derive d;

    cout <<"b1:" <<sizeof(b1) << endl;   //虚表指针大小+sizeof(int)
    cout << "b2:" << sizeof(b2) << endl; //虚表指针大小+sizeof(int)
    cout <<"d:" << sizeof(d) << endl;    //两个虚表指针大小+3*sizeof(int)

    return 0;
}
/*
程序结果:
b1:8
b2:8
d:20
*/

QQ图片20190917174548

 

简单虚继承

虚继承可以解决菱形继承的情况

原则:

  1.虚继承的子类,如果本身定义了新的虚函数,则编译器为其生成一个新的虚函数指针(vptr)以及一张虚函数表,该vptr位于对象内存的前面(对比非虚继承:直接扩展父类虚函数表)

  2.虚继承的子类也单独保留了父类的vptr和虚函数表

  3.虚继承的子类有虚基类表指针(vbptr)

ps:在c++对象模型中,虚继承而来的子类会生成一个隐藏的虚基类指针(vbptr),虚基类表指针总是在虚函数表指针之后,因而对于某个实例来说,如果它有虚基类指针,那么虚基类指针可能在0字节偏移处(该类没有vptr,vbptr就位于实例内存布局的最前面,否则vptr位于最前面),也可能存在类实例的4字节偏移处

461913-20160806182912481-2027577739

461913-20160806182928247-1108080872

样例:

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void fun1() {}
    virtual void fun2() {}
private:
    int m_base;
};

class Derive : virtual public Base
{
public:
    void fun1() {}
    virtual void fun3() {}
private:
    int m_derive;
};

int main()
{
    Base b;
    Derive d;
    return 0;
}

QQ图片20190917195236

 

菱形虚继承

菱形虚继承是多继承和虚继承的复合

内存模型如下:

QQ图片20190917200639

 

 

posted @ 2019-09-17 20:09  西*风  阅读(697)  评论(0编辑  收藏  举报