什么是对象模型

有两个概念可以解释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. 若子声明了自己新的虚函数,则该虚函数地址将扩充到虚函数表最后
 #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(); system("pause");
return ;
}

输出:

调试:

对象模型:

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

一般多继承

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

原则:

  1. 若子类新增虚函数,放在声明的第一个父类的虚函数表中
  2. 若子类重写了父类的虚函数,所有父类的虚函数表都要改变:如fun1
  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;
cout << "b2:" << sizeof(b2) << endl;
cout <<"d:" << sizeof(d) << endl;
system("pause");
return ;
}

输出:

各个类对象的大小

调试:注意观察fun1

对象模型:

简单虚继承

原则:

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

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

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

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

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

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

 #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; system("pause");
return ;
}

对象模型:

 

菱形虚继承

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

笔记原图:

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

C++对象模型:单继承,多继承,虚继承的更多相关文章

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

    C++目前使用的对象模型: 此模型下,nonstatic数据成员被置于每一个类的对象中,而static数据成员则被置于类对象之外,static和nonstatic函数也都放在类对象之外(通过函数指针指 ...

  2. C++中的类继承(4)继承种类之单继承&多继承&菱形继承

    单继承是一般的单一继承,一个子类只 有一个直接父类时称这个继承关系为单继承.这种关系比较简单是一对一的关系: 多继承是指 一个子类有两个或以上直接父类时称这个继承关系为多继承.这种继承方式使一个子类可 ...

  3. C++虚继承初识

    struct Employee { ... }; struct Manager : Employee { ... }; struct Worker : Employee { ... }; struct ...

  4. 关于虚继承的sizeof问题

    首先关于虚继承和普通继承的知识,我总结一下: 1.普通继承时,无论派生类是否定义新的虚函数,基类和派生类总是共享一个虚函数表,不需要另加指向虚函数的指针,派生类只是将虚函数表中的元素改成了派生类的地址 ...

  5. C++继承,多重继承,虚继承的构造函数以及析构函数的调用顺序问题

    #include <iostream> using namespace std; class A{ int data_a; public: A(){ data_a = ; cout < ...

  6. C++ 继承之虚继承与普通继承的内存分布

    仅供互相学习,请勿喷,有观点欢迎指出~ class A { virtual void aa(){}; }; class B : public virtual A { ]; //加入一个变量是为了看清楚 ...

  7. 多重继承,虚继承,MI继承中虚继承中构造函数的调用情况

    先来测试一些普通的多重继承.其实这个是显而易见的. 测试代码: //测试多重继承中派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include < ...

  8. C++学习之虚函数继承和虚继承

    虚函数的定义要遵循以下重要规则: 1.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行晚绑定的. 2.只有类的成员函 ...

  9. (C/C++学习)5.C++中的虚继承-虚函数-多态解析

    说明:在C++学习的过程中,虚继承-虚函数经常是初学者容易产生误解的两个概念,它们与C++中多态形成的关系,也是很多初学者经常产生困惑的地方,这篇文章将依次分别对三者进行解析,并讲述其之间的联系与不同 ...

随机推荐

  1. 【Mysql】 my.ini配置一例

    # For advice on how to change settings please see # http://dev.mysql.com/doc/refman/5.6/en/server-co ...

  2. oracle系统包—-dbms_output用法

    dbms_output包主要用于调试pl/sql程序,或者在sql*plus命令中显示信息(displaying message)和报表,譬如我们可以写一个简单的匿名pl/sql程序块,而该块出于某种 ...

  3. Android 长按Listview显示CheckBox,实现批量删除。

    ListView实现的列表,如果是可编辑,可删除的,一般都要提供批量删除功能,否则的话,一项一项的删除体验很不好,也给用户带来了很大的麻烦. 实现效果图 具体实现代码 select.xml 主布局文件 ...

  4. Android使用service后台更新计划任务

    Service是Android的四大组件之一,这里就不再过多的去描述,下面主要实现启动应用时候利用service后台执行计划任务,退出应用后,关闭service,只存在整个应用的周期中. 首先使用se ...

  5. [CareerCup] 4.9 All Paths Sum 所有路径和

    4.9 You are given a binary tree in which each node contains a value. Design an algorithm to print al ...

  6. Cordova开发总结(插件篇)

    最近刚刚做完一个用Cordova开发了一款电子商务的应用.在选用Cordova前,我有考察过,国内的Appcan, Apicloud等等的解决方案.其实Appcan,ApiCloud的混合方案挺完整的 ...

  7. Unity 3D本地发布WebPlayer版时Failed to download data file解决方案

    遇到这个问题就是指Web服务器并没有支持这种*.unity3d文件类型.需要做的是在Web服务器中添加MIME类型: IIS 7 及以上版本: 在功能视图的IIS选项卡中: 双击打开MIME,选择添加 ...

  8. [poj2184]我是来水一下背包的

    http://poj.org/problem?id=2184 题意:01背包的变种,就是说有2组值(有负的),你要取一些物品是2阻值的和非负且最大 分析: 1.对于负的很好处理,可以把他们都加上一个数 ...

  9. 第五次课堂总结x

    一.知识点: 1.while语句 循环体语句:           while语句里的表达式可以是任何合法的表达式,循环体则只可以表达一条语句. while的循环体语句需要能改变循环条件的真假条件. ...

  10. jQuery Mobile学习日记

    本次主讲人是王思伦啦啦啦~ 框架特性 jQuery Mobile 以“Write Less, Do More”作为目标,为所有的主流移动操作系统平台提供了高度统一的 UI 框架:jQuery 的移动框 ...