什么是对象模型

有两个概念可以解释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. 去他的效应(what-the-hell effect)与自我放纵

    去他的 效应(what-the-hell effect)与自我放纵 为什么写这篇文章: 对于我来说,但我感到疲惫——"无意拿起"手机,对自己说"随便看看"——但 ...

  2. webapi 获取请求参数

    //获取请求消息提数据 Stream stream = filterContext.Request.Content.ReadAsStreamAsync().Result; Encoding encod ...

  3. 简单通用JDBC辅助类封装

    哎,最近很好久没在博客园写点东西了,由于工作的原因,接触公司自己研发的底层orm框架,偶然发现该框架在调用jdbc操作的时候参考的是hibernate 里面的SimpleJdbcTemplate,这里 ...

  4. Linux及安全课程——相关链接总结

    附录:学习笔记链接总结 MOOC课程学习笔记与实验: 第一周:计算机是如何工作的 第二周:操作系统是怎么工作的 -- 一个简单的时间片轮转多道程序内核代码及分析 第三周:构造一个简单的Linux系统M ...

  5. 20135220谈愈敏Linux_总结

    Linux_总结 具体博客链接 计算机是如何工作的 操作系统是如何工作的 构造一个简单的Linux系统MenuOS 系统调用(上) 系统调用(下) 进程的描述和创建 可执行程序的装载 进程的切换和系统 ...

  6. 推荐一个 angular 图像加载插件

    推荐一个简单的 Angular 图片加载插件:vgSrc,插件根据图片资源的不同加载状态,显示不同图片,亲测兼容IE-8. 使用 推荐使用 bower 加载: bash bower install v ...

  7. PDA设备小知识--(IP)工业防护等级含义

    IP(INTERNATIONAL PROTECTION)防护等级是专门的工业防护等级,,它将电器依其防尘.防湿气之特性加以分级.IP防护等级是由两个数字所组成,第1个数字表示电器离尘.防止外物侵入的等 ...

  8. Object C学习笔记25-文件管理(一)

    在此篇文章中简单记录一下文件管理,在Object C中NSFileManager用于管理文件已经路径.在Object C中的文件路径可以是相对路径也可以是绝对路径.斜线“/”开头,斜线实际上就是一个目 ...

  9. node的实践(项目三)

    渲染前台的方式. <!DOCTYPE html> <html> <head> <meta http-equiv="content-type" ...

  10. jQuery找兄弟系列next(),nextAll(),nextUntil(),prev(),prevAll(),prevUntil(),siblings()

    <body> <div id="main"> <div id="hot" class="rightbar"&g ...