C++对象模型3--无重写的单继承
C++对象模型中加入单继承
不管是单继承、多继承,还是虚继承,如果基于“简单对象模型”,每一个基类都可以被派生类中的一个slot指出,该slot内包含基类对象的地址。这个机制的主要缺点是,因为间接性而导致空间和存取时间上的额外负担;优点则是派生类对象的大小不会因其基类的改变而受影响。
如果基于“表格驱动模型”,派生类中有一个slot指向基类表,表格中的每一个slot含一个相关的基类地址(这个很像虚函数表,内含每一个虚函数的地址)。这样每个派生类对象汗一个bptr,它会被初始化,指向其基类表。这种策略的主要缺点是由于间接性而导致的空间和存取时间上的额外负担;优点则是在每一个派生类对象中对继承都有一致的表现方式,每一个派生类对象都应该在某个固定位置上放置一个基类表指针,与基类的大小或数量无关。第二个优点是,不需要改变派生类对象本身,就可以放大,缩小、或更改基类表。
不管上述哪一种机制,“间接性”的级数都将因为集成的深度而增加。C++实际模型是,对于一般继承是扩充已有存在的虚函数表;对于虚继承添加一个虚函数表指针。
无重写的单继承
无重写,即派生类中没有于基类同名的虚函数。
基类和派生类的代码:
//Base.h
#pragma once
#include<iostream>
using namespace std; class Base
{
public:
Base(int);
virtual ~Base(void);
virtual void print(void) const; protected:
int iBase;
};
//Base.cpp
#include "Base.h" Base::Base(int i)
{
iBase = 1;
cout << "Base_1::Base()" << endl;
} Base::~Base(void)
{
cout << "Base::~Base()" << endl;
} void Base::print(void) const
{
cout<<"Base_1::print(), iBase " << iBase << endl;
}
//Derived.h
#pragma once
#include "base.h" class Derived : public Base
{
public:
Derived(int);
virtual ~Derived(void);
virtual void derived_print(void); protected:
int iDerived;
};
//Derived.cpp
#include "Derived.h" Derived::Derived(int i) : Base(0)
{
iDerived = i;
cout<<"Derived::Derived()"<<endl;
} Derived::~Derived(void)
{
cout<<"Derived::~Derived()"<<endl;
} void Derived::derived_print()
{
cout<<"Derived::derived_print()"<<iDerived<<endl;
}
Base、Derived的类图如下所示:

Base的模型跟上面的一样,不受继承的影响。Derived不是虚继承,所以是扩充已存在的虚函数表,所以结构如下图所示:

为了验证上述C++对象模型,我们编写如下测试代码。
void test_single_inherit_norewrite()
{
Derived d(9999); cout << "对象d的起始内存地址:" << &d << endl; //获取类型信息
cout << "type_info信息的地址:" << ((int*)*(int*)(&d) - 1) << endl; //cout得到一个地址,就输出这个地址里存放的内容
RTTICompleteObjectLocator str = *((RTTICompleteObjectLocator*)*((int*)*(int*)(&d) - 1));
string classname(str.pTypeDescriptor->name);
cout << classname << endl; //获取虚函数信息 cout << "虚函数表地址:" << (int*)(&d) << endl;
cout << "虚函数表中第1个函数占位符的地址:" << (int*)*(int*)(&d) << "即析构函数在虚函数表中占位符的地址" << endl;
cout << "虚函数表中第2个函数占位符的地址:" << ((int*)*(int*)(&d) + 1) << endl;
typedef void(*Fun)(void);
Fun pFun = (Fun)*((int*)*(int*)(&d) + 1);
pFun();
d.print();
cout << endl; cout << "虚函数表中第3个函数占位符的地址:" << ((int*)*(int*)(&d) + 2) << endl;
pFun = (Fun)*((int*)*(int*)(&d) + 2);
pFun();
d.derived_print();
cout << endl; //获取成员变量的信息
cout << "推测成员变量iBase的地址:" << (int*)(&d) + 1 << endl;
cout << "通过地址取得的iBase的值:" << *((int*)(&d) + 1) << endl; cout << "推测成员变量iDerived地址:" << (int*)(&d) + 2 << endl;
cout << "通过地址取得的iDerived的值:" << *((int*)(&d) + 2) << endl; }
为了支持RTTICompleteObjectLocator必须引入一个头文件,这个头文件中定义了一些结构体,这些结构体封装了类的相关信息。
//type_info.h
#pragma once typedef unsigned long DWORD;
struct TypeDescriptor
{
DWORD ptrToVTable;
DWORD spare;
char name[8];
};
struct PMD
{
int mdisp; //member displacement
int pdisp; //vbtable displacement
int vdisp; //displacement inside vbtable
};
struct RTTIBaseClassDescriptor
{
struct TypeDescriptor* pTypeDescriptor; //type descriptor of the class
DWORD numContainedBases; //number of nested classes following in the Base Class Array
struct PMD where; //pointer-to-member displacement info
DWORD attributes; //flags, usually 0
}; struct RTTIClassHierarchyDescriptor
{
DWORD signature; //always zero?
DWORD attributes; //bit 0 set = multiple inheritance, bit 1 set = virtual inheritance
DWORD numBaseClasses; //number of classes in pBaseClassArray
struct RTTIBaseClassArray* pBaseClassArray;
}; struct RTTICompleteObjectLocator
{
DWORD signature; //always zero ?
DWORD offset; //offset of this vtable in the complete class
DWORD cdOffset; //constructor displacement offset
struct TypeDescriptor* pTypeDescriptor; //TypeDescriptor of the complete class
struct RTTIClassHierarchyDescriptor* pClassDescriptor; //describes inheritance hierarchy
};
测试结果:

注意:有一个点需要说明一下,从代码和执行的结果中可以看出通过函数指针调用的函数和通过对象调用的函数,打印的成员变量的值是不一样的,具体的原因是C++在调用成员函数的时候,会把某一个对象传递给一个成员函数隐藏的函数参数this指针。这样,这个成员函数就知道了去操作哪一个对象的数据,但是通过函数指针调用成员函数的话,就没有一个对象来初始化这个成员函数中的this指针。所以通过函数指针调用成员函数是找不到具体的操作对象的,所以打印的值是一个随机值。
C++对象模型3--无重写的单继承的更多相关文章
- C++对象模型4--有重写的单继承
有重写的单继承 派生类中重写了基类的print()函数. //Derived_Overwrite.h #pragma once #include "base.h" class De ...
- C++对象模型:单继承,多继承,虚继承
什么是对象模型 有两个概念可以解释C++对象模型: 语言中直接支持面向对象程序设计的部分.对于各种支持的底层实现机制. 类中成员分类 数据成员分为静态和非静态,成员函数有静态非静态以及虚函数 clas ...
- C++对象模型:单继承,多继承,虚继承,菱形虚继承,及其内存布局图
C++目前使用的对象模型: 此模型下,nonstatic数据成员被置于每一个类的对象中,而static数据成员则被置于类对象之外,static和nonstatic函数也都放在类对象之外(通过函数指针指 ...
- C++中的类继承(4)继承种类之单继承&多继承&菱形继承
单继承是一般的单一继承,一个子类只 有一个直接父类时称这个继承关系为单继承.这种关系比较简单是一对一的关系: 多继承是指 一个子类有两个或以上直接父类时称这个继承关系为多继承.这种继承方式使一个子类可 ...
- python基础学习笔记——单继承
1.为什么要有类的继承性?(继承性的好处)继承性的好处:①减少了代码的冗余,提供了代码的复用性②提高了程序的扩展性 ③(类与类之间产生了联系)为多态的使用提供了前提2.类继承性的格式:单继承和多继承# ...
- c++继承汇总(单继承、多继承、虚继承、菱形继承)
多重继承中,一个基类可以在派生层次中出现多次,如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多分同名成员.C++提供虚基类的方法使得在 ...
- C++单继承、多继承情况下的虚函数表分析
C++的三大特性之一的多态是基于虚函数实现的,而大部分编译器是采用虚函数表来实现虚函数,虚函数表(VTAB)存在于可执行文件的只读数据段中,指向VTAB的虚表指针(VPTR)是包含在类的每一个实例当中 ...
- Python进阶(十六)----面向对象之~封装,多态,鸭子模型,super原理(单继承原理,多继承原理)
Python进阶(十六)----面向对象之~封装,多态,鸭子模型,super原理(单继承原理,多继承原理) 一丶封装 , 多态 封装: 将一些东西封装到一个地方,你还可以取出来( ...
- java类为什么是单继承。类的继承,实现接口。
java中提供类与类之间提供单继承. 提供多继承会可能出现错误,如:一个类继承了两个父类,而两个父类里面都有show()方法. class Fulei1{ public void show(){ Sy ...
随机推荐
- Java用Dijkstra算法实现地图两点的最短路径查询(Android版)
地图上实现最短路径的查询,据我了解的,一般用Dijkstra算法和A*算法来实现.由于这是一个课程项目,时间比较急,而且自己不熟悉A*算法,所以参考网上的Dijkstra算法(http://blog. ...
- lucene3.6笔记添加搜索功能
lucene为程序添加搜索功能,此功能基于已创建好的文档的索引之上.这里我已经为一些文档建立了索引,并保存到硬盘上.下面开始针对这些索引,添加搜索功能. 1.简单的TermQuery搜索 Java代码 ...
- dataset 用法(3)
ReadXml 提供了只将数据或同时将数据和架构从 XML 文档读入 DataSet 的方式(若要同时读数据和架构,请使用包括 mode 参数的 ReadXML 重载之一,并将其值设置为 ReadSc ...
- SQL Server索引进阶:第十五级,索引的最佳实践
在本文中我们将推荐14条贯穿本系列的规则,这些规则帮助你为数据库创建最好的索引结构. 格式来自于<Framework Design Guidelines>.每条推荐用四个词来总结:Do做, ...
- Win10下Genymotion无法正常使用的解决方法
原Win7下安装配置的genymotion正常使用,Eclipse的Genymotion插件也可以正常运行.系统升级后,忽然就不work了. 折腾了一天试了各种方式,网上的例子也五花八门.最后还是找到 ...
- oracle 与sql server 部分内置函数替换
1,trunc--convert oracle: select trunc(sysdate) from dual 输出:2016/5/24 sql server:select CONVERT(va ...
- HTML中小问题
1.a标签不添加href这个属性时,不会出现鼠标变成手型~
- 表单元素的submit()方法和onsubmit事件(转)
1.表单元素中出现了name="submit"的元素 2.elemForm.submit();不会触发表单的onsubmit事件 3.动态创建表单时遇到的问题 表单元素拥有subm ...
- 简单使用 PHP Phar 打包php代码 笔记
Phar简介:Phar 归档的概念来自 Java™ 技术的 JAR 归档,它允许使用单个文件打包应用程序,这个文件中包含运行应用程序所需的所有东西.该文件不同于单个可执行文件,后者通常由编程语言生成, ...
- C#学习日志 day 2 plus ------ hyper-V 开启方法
hyper-V的开启需要两个步骤. 第一是在bios中开启 virtualization technology--虚拟化技术 在process setting中改为enabled. 进入bios界面的 ...