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 ...
随机推荐
- golang仿AS3写的ByteArray
用golang写了个仿AS3写的ByteArray,稍微有点差别,demo能成功运行,还未进行其他测试 主要参考的是golang自带库里的Buffer,结合了binary 来看看demo: packa ...
- php导出CSV时,超长数字精度丢失问题与前导0的字符串丢失0的问题解决
php生成的CSV有时候会遇到两个特殊情况: 1.输出的字段中,含有超长数字(18位的数字)比方身份证:122121197410180016,就算输出时字段加上"",还是会被识别成 ...
- JSP简单练习-获取表单数据
在JSP中,server端程序与client交互最经常使用的方法就是採用表单提交数据.表单提交的方法主要有两种,一种是get方法.还有一种是post方法.两者最大的差别:使用get方法提交的数据会显示 ...
- JavaScript之充实文档的内容
1.我们在平时的开发中会碰到一些缩略语如:XML,HTML,API等专业术语:为了能使用户,更好的了解术语的意思,我们通常会给<abbr></abbr>标签加一个title属性 ...
- vs2012新建项目产生的问题
当用vs新建web项目时遇到
- Javascript知识三
O(∩_∩)O...今天继续学习了Javascript的知识,下面就总结一下. 数组: 判断是否为数组(instanceof Array) 在JavaScript中,数组不一定是同一类型 var ar ...
- jvm栈和堆详解
Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间 ...
- WinForm中快捷键与组合按键的设置方法
每个窗体都有这样3个事件:KeyDown.KeyPress.KeyUp,KeyDown和KeyPress都是按键按下事件,但KeyDown用的是KeyCode跟键盘各个按键相对应,它对应Keys枚举, ...
- Unity StrangeIoc框架 (三)signal信号方式
先创建TestRoot using UnityEngine; using System.Collections; using strange.extensions.context.impl; publ ...
- 如何升级CentOS 6.5下的MySQL
如何升级CentOS 6.5下的MySQL http://jingyan.baidu.com/article/48a42057e9b9bca9242504ab.html | 浏览:1136 | 更新: ...