• C++对象模型:单继承,多继承,虚继承


    什么是对象模型

    有两个概念可以解释C++对象模型:

    语言中直接支持面向对象程序设计的部分。
    对于各种支持的底层实现机制。

    类中成员分类

    数据成员分为静态和非静态,成员函数有静态非静态以及虚函数

    class data members:static和nonstatic

    class data functions:static、nonstatic和virtual

    比如:

    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;
    };

    对象模型分类

    简单对象模型:这个模型非常地简单粗暴。在该模型下,对象由一系列的指针组成,每一个指针都指向一个数据成员或成员函数,也即是说,每个数据成员和成员函数在类中所占的大小是相同的,都为一个指针的大小。这样有个好处——很容易算出对象的大小,不过赔上的是空间和执行期效率。所以这种对象模型并没有被用于实际产品上。

    表格驱动对象模型:把类中的数据分成了两个部分:数据部分与函数部分,并使用两张表格,一张存放数据本身,一张存放函数的地址(也即函数比成员多一次寻址),而类对象仅仅含有两个指针,分别指向上面这两个表。这样看来,对象的大小是固定为两个指针大小。这个模型也没有用于实际应用于真正的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是为多态而生成的信息,包括对象继承关系,对象本身的描述等,只有具有虚函数的对象在会生成。

    单继承(父类含虚函数)

    原则:

    对普通单继承而言

    1. 子类与父类拥有各自的一个虚函数表
    2. 若子类并无overwrite父类虚函数,用父类虚函数
    3. 若子类重写(overwrite)了父类的虚函数,则子类虚函数将覆盖虚表中对应的父类虚函数
    4. 若子声明了自己新的虚函数,则该虚函数地址将扩充到虚函数表最后
     1 #include <iostream>
     2 using namespace std;
     3 
     4 class Base
     5 {
     6 public:
     7     virtual void fun1(){ cout << "Base fun1" << endl; }
     8     virtual void fun2(){ cout << "Base fun2" << endl; }
     9 private:
    10     int a;
    11 };
    12 
    13 class Derive :  public Base
    14 {
    15 public:
    16     void fun2(){ cout << "Derive fun2" << endl; }
    17     virtual void fun3(){}
    18 private:
    19     int b;
    20 };
    21 
    22 int main()
    23 {
    24     Base b;
    25     Derive d;
    26     Base *p = &d;
    27     p->fun1();
    28     p->fun2();
    29 
    30     system("pause");
    31     return 0;
    32 }

    输出:

     

    调试:

    对象模型:

    事实上vs调试并不能看到完整信息(比如virtual fun3以及之后提到到虚基类指针),正确的应该是

    一般多继承

    这里讲的是不考虑菱形继承的多继承,因为菱形继承需要用到虚继承,放到之后考虑

    原则:

    1. 若子类新增虚函数,放在声明的第一个父类的虚函数表中
    2. 若子类重写了父类的虚函数,所有父类的虚函数表都要改变:如fun1
    3. 内存布局中,父类按照其声明顺序排列
     1 #include <iostream>
     2 using namespace std;
     3 
     4 class Base1
     5 {
     6 public:
     7     virtual void fun1(){}
     8 private:
     9     int m_base1;
    10 };
    11 
    12 class Base2
    13 {
    14 public:
    15     virtual void fun1(){}
    16     virtual void fun2(){}
    17 private:
    18     int m_base2;
    19 };
    20 
    21 class Derive :  public Base1,public Base2
    22 {
    23 public:
    24     void fun1(){}
    25     virtual void fun3(){}
    26 private:
    27     int m_derive;
    28 };
    29 
    30 int main()
    31 {
    32     Base1 b1;
    33     Base2 b2;
    34     Derive d;
    35 
    36     cout <<"b1:" <<sizeof(b1) << endl;
    37     cout << "b2:" << sizeof(b2) << endl;
    38     cout <<"d:" << sizeof(d) << endl;
    39     system("pause");
    40     return 0;
    41 }

    输出:

    各个类对象的大小

    调试:注意观察fun1

    对象模型:

    简单虚继承

    原则:

    虚继承解决了菱形继承中最派生类拥有多个间接父类实例的情况

    1. 虚继承的子类,如果本身定义了新的虚函数,则编译器为其生成一个新的虚函数指针(vptr)以及一张虚函数表。该vptr位于对象内存最前面(对比非虚继承:直接扩展父类虚函数表)
    2. 虚继承的子类也单独保留了父类的vprt与虚函数表
    3. 虚继承的子类有虚基类表指针(vbptr)

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

     

    虚基类表也由多个条目组成,条目中存放的是偏移值。

    第一个条目存放虚基类表指针(vbptr)所在地址到该类内存首地址的偏移值

    第二、第三...个条目依次为该类的最左虚继承父类、次左虚继承父类...的内存地址相对于虚基类表指针的偏移值。

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class Base
     5 {
     6 public:
     7     virtual void fun1(){}
     8     virtual void fun2(){}
     9 private:
    10     int m_base;
    11 };
    12 
    13 class Derive : virtual public Base
    14 {
    15 public:
    16     void fun1(){}
    17     virtual void fun3(){}
    18 private:
    19     int m_derive;
    20 };
    21 
    22 int main()
    23 {
    24     Base b;
    25     Derive d;
    26 
    27     system("pause");
    28     return 0;
    29 }

    对象模型:

     

    菱形虚继承

    菱形虚继承是多继承和虚继承的复合,直接画一个对象模型吧:

    笔记原图:

    参考:图说C++对象模型:对象内存布局详解

  • 相关阅读:
    Java Object part1
    StringBuffer StringBuilder append
    Java equal
    java Classloader
    Java NIO
    Oracle中Blob和Clob
    Java8 Lambda 表达式
    HashMap分析 + 哈希表
    android自定义控件之滚动广告条
    android自定义控件之模仿优酷菜单
  • 原文地址:https://www.cnblogs.com/raichen/p/5744300.html
Copyright © 2020-2023  润新知