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 ...
随机推荐
- [Leetcode][Python]28: Implement strStr()
# -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com' 28: Implement strStr()https://oj.leetco ...
- dataGuard client 自动切换
使用dataguard作为HA方案,要解决的一个问题在于:后台数据库发生了切换,client连接如何做到自动切到新的primary数据库上? 如果做通用的方案,需要客户端自己提供自动重连的能力,这点大 ...
- 基于Qt的图像采集系统
硬件 Point Gray Camera 型号:FL3-U3-13S2C-CS 参数 Sony IMX035 CMOS, 1/3", 3.63 µm Rolling Shutter 1328 ...
- Codechef Nuclear Reactors 题解
There are K nuclear reactor chambers labelled from 0 to K-1. Particles are bombarded onto chamber 0. ...
- hdu1114Piggy-Bank(DP完全背包)
题意:在ACM可以做任何事情,必须准备和预算获得必要的财政支持.这次行动的主要收入来自不可逆绑定金钱(IBM).背后的想法很简单.每当一些ACM成员有任何小的钱,他把所有的硬币和成小猪银行抛出.你知道 ...
- onclick=‘’return false“
文章来自 http://www.cnblogs.com/hellen-li/archive/2010/10/22/1858422.html checkbox没有readOnly属性,可以这样让它保持 ...
- 一个简单的Spring AOP例子
转载自: http://www.blogjava.net/javadragon/archive/2006/12/03/85115.html 经过这段日子的学习和使用Spring,慢慢地体会到Sprin ...
- Linux 文件内容转码
文件内容的转换: iconv -f GB2312 -t UTF-8 gb1.txt >gb2.txt-f, –from-code=名称 原始文本编码-t, –to-code=名称 输出编码-o, ...
- WCF编写时候的测试
1右击WCF创建到使用到发布这篇文章中的类库项目中的接口类实现文件添加断点 2右击WCF创建到使用到发布这篇文章中的WCF服务网站设为启动项并允许 3右击WCF创建到使用到发布这篇文章中的WPF项目调 ...
- C# 微信公众平台开发(1)-- 服务器配置
题记:最近公司需要开发微信服务号,由本人负责,以前虽然听过微信开发,但并没有认真的去了解,项目开发中,也边看文档边开发,记录自己的项目开发经验: 1.注册帐号--填写服务器配置 在https://mp ...