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 函数,则通过虚函数表+虚指针来支持:
- 每个类生成一个表格,称为虚表(virtual table,简称vtbl)。虚表中存放着一堆指针,这些指针指向该类每一个虚函数。虚表中的函数地址将按声明时的顺序排列
- 每个类对象都拥有一个虚表指针(vptr),由编译器为其生成。虚表指针的设定与重置皆由类的复制控制(也即是构造函数、析构函数、赋值操作符)来完成。vptr的位置为编译器决定,传统上它被放在所有显示声明的成员之后,不过现在许多编译器把vptr放在一个类对象的最前端(也就是说对象的地址就是vptr的地址)
- 虚函数表的前面设置了一个指向type_info的指针,用以支持RTTI(Run Time Type Identification,运行时类型识别)。RTTI是为多态而生成的信息,包括对象继承关系,对象本身的描述等,只有具有虚函数的对象在会生成。

单继承(父类含虚函数)
原则:
对普通单继承而言
- 子类与父类拥有各自的一个虚函数表
- 若子类并无overwrite父类虚函数,用父类虚函数
- 若子类重写(overwrite)了父类的虚函数,则子类虚函数将覆盖虚表中对应的父类虚函数
- 若子声明了自己新的虚函数,则该虚函数地址将扩充到虚函数表最后
#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以及之后提到到虚基类指针),正确的应该是

一般多继承
这里讲的是不考虑菱形继承的多继承,因为菱形继承需要用到虚继承,放到之后考虑
原则:
- 若子类新增虚函数,放在声明的第一个父类的虚函数表中
- 若子类重写了父类的虚函数,所有父类的虚函数表都要改变:如fun1
- 内存布局中,父类按照其声明顺序排列
#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

对象模型:

简单虚继承
原则:
虚继承解决了菱形继承中最派生类拥有多个间接父类实例的情况
- 虚继承的子类,如果本身定义了新的虚函数,则编译器为其生成一个新的虚函数指针(vptr)以及一张虚函数表。该vptr位于对象内存最前面(对比非虚继承:直接扩展父类虚函数表)
- 虚继承的子类也单独保留了父类的vprt与虚函数表
- 虚继承的子类有虚基类表指针(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++对象模型:单继承,多继承,虚继承,菱形虚继承,及其内存布局图
C++目前使用的对象模型: 此模型下,nonstatic数据成员被置于每一个类的对象中,而static数据成员则被置于类对象之外,static和nonstatic函数也都放在类对象之外(通过函数指针指 ...
- C++中的类继承(4)继承种类之单继承&多继承&菱形继承
单继承是一般的单一继承,一个子类只 有一个直接父类时称这个继承关系为单继承.这种关系比较简单是一对一的关系: 多继承是指 一个子类有两个或以上直接父类时称这个继承关系为多继承.这种继承方式使一个子类可 ...
- C++虚继承初识
struct Employee { ... }; struct Manager : Employee { ... }; struct Worker : Employee { ... }; struct ...
- 关于虚继承的sizeof问题
首先关于虚继承和普通继承的知识,我总结一下: 1.普通继承时,无论派生类是否定义新的虚函数,基类和派生类总是共享一个虚函数表,不需要另加指向虚函数的指针,派生类只是将虚函数表中的元素改成了派生类的地址 ...
- C++继承,多重继承,虚继承的构造函数以及析构函数的调用顺序问题
#include <iostream> using namespace std; class A{ int data_a; public: A(){ data_a = ; cout < ...
- C++ 继承之虚继承与普通继承的内存分布
仅供互相学习,请勿喷,有观点欢迎指出~ class A { virtual void aa(){}; }; class B : public virtual A { ]; //加入一个变量是为了看清楚 ...
- 多重继承,虚继承,MI继承中虚继承中构造函数的调用情况
先来测试一些普通的多重继承.其实这个是显而易见的. 测试代码: //测试多重继承中派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include < ...
- C++学习之虚函数继承和虚继承
虚函数的定义要遵循以下重要规则: 1.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行晚绑定的. 2.只有类的成员函 ...
- (C/C++学习)5.C++中的虚继承-虚函数-多态解析
说明:在C++学习的过程中,虚继承-虚函数经常是初学者容易产生误解的两个概念,它们与C++中多态形成的关系,也是很多初学者经常产生困惑的地方,这篇文章将依次分别对三者进行解析,并讲述其之间的联系与不同 ...
随机推荐
- [CareerCup] 3.4 Towers of Hanoi 汉诺塔
3.4 In the classic problem of the Towers of Hanoi, you have 3 towers and N disks of different sizes ...
- [CareerCup] 8.6 Jigsaw Puzzle 拼图游戏
8.6 Implement a jigsaw puzzle. Design the data structures and explain an algorithm to solve the puzz ...
- LINUX信息安全系统设计基础第二周学习总结
1 Linux命令 2 man命令 1.Terminal(终端) Linux 系统还提供了一个叫做终端模拟器的程序(Terminal),下面几个比较常见的终端模拟器,例如 gnome-terminal ...
- eclipse的一些初始化设置
eclipse下载地址:http://www.eclipse.org/ 添加java注释模板 codetemplates.xml:Window->Preferences->Java-> ...
- Sqlite3 设置插入触发器
需求: 数据库中表t_VerifyCsmDetail需要最多保存10W条记录,超出时删除最旧的那一条. 思路:设置插入触发器.插入前先判断表中记录总数,如果大于99999条,则删除最旧的一条记录. 代 ...
- css为什么要用悬浮
如果这个100px的宽度是一个Img,横内元素.右边也是横内元素的话和块级元素是不同的. 2.文字环绕图片. div下有个img然后有个span标签 img最好悬浮,悬浮虽然说脱离文档,但是还是占空间 ...
- Boostrap响应式与非响应式
非响应式布局 在使用非响应式布局时,在<head>标签中需要加入一下内容,其中最主要的是non-responsive.css文件 <head> <meta http-eq ...
- javascript继承(二)—创建对象的三种模式
一.工厂模式 function createPerson(name,age){ var o = {}; o.name = name; o.age = age; o.sayHi = function() ...
- AngularJs-指令和指令之间的交互(动感超人)
前言: 上节我们学习到了指令和控制器之间的交互,通过给指令添加动作,调用了控制器中的方法.本节我们学习指令和指令之间是如何交互的,我们通过一个小游戏来和大家一起学习,听大漠老师说这是国外的人写的dem ...
- angular例子笔记
学习angular的插件写法和制作; <!DOCTYPE html> <html ng-app="APP"> <head> <meta c ...