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--无重写的单继承的更多相关文章

  1. C++对象模型4--有重写的单继承

    有重写的单继承 派生类中重写了基类的print()函数. //Derived_Overwrite.h #pragma once #include "base.h" class De ...

  2. C++对象模型:单继承,多继承,虚继承

    什么是对象模型 有两个概念可以解释C++对象模型: 语言中直接支持面向对象程序设计的部分.对于各种支持的底层实现机制. 类中成员分类 数据成员分为静态和非静态,成员函数有静态非静态以及虚函数 clas ...

  3. C++对象模型:单继承,多继承,虚继承,菱形虚继承,及其内存布局图

    C++目前使用的对象模型: 此模型下,nonstatic数据成员被置于每一个类的对象中,而static数据成员则被置于类对象之外,static和nonstatic函数也都放在类对象之外(通过函数指针指 ...

  4. C++中的类继承(4)继承种类之单继承&多继承&菱形继承

    单继承是一般的单一继承,一个子类只 有一个直接父类时称这个继承关系为单继承.这种关系比较简单是一对一的关系: 多继承是指 一个子类有两个或以上直接父类时称这个继承关系为多继承.这种继承方式使一个子类可 ...

  5. python基础学习笔记——单继承

    1.为什么要有类的继承性?(继承性的好处)继承性的好处:①减少了代码的冗余,提供了代码的复用性②提高了程序的扩展性 ③(类与类之间产生了联系)为多态的使用提供了前提2.类继承性的格式:单继承和多继承# ...

  6. c++继承汇总(单继承、多继承、虚继承、菱形继承)

    多重继承中,一个基类可以在派生层次中出现多次,如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多分同名成员.C++提供虚基类的方法使得在 ...

  7. C++单继承、多继承情况下的虚函数表分析

    C++的三大特性之一的多态是基于虚函数实现的,而大部分编译器是采用虚函数表来实现虚函数,虚函数表(VTAB)存在于可执行文件的只读数据段中,指向VTAB的虚表指针(VPTR)是包含在类的每一个实例当中 ...

  8. Python进阶(十六)----面向对象之~封装,多态,鸭子模型,super原理(单继承原理,多继承原理)

    Python进阶(十六)----面向对象之~封装,多态,鸭子模型,super原理(单继承原理,多继承原理) 一丶封装 , 多态 封装:            将一些东西封装到一个地方,你还可以取出来( ...

  9. java类为什么是单继承。类的继承,实现接口。

    java中提供类与类之间提供单继承. 提供多继承会可能出现错误,如:一个类继承了两个父类,而两个父类里面都有show()方法. class Fulei1{ public void show(){ Sy ...

随机推荐

  1. dijkstra 优先队列最短路模板

    ;;*maxn];,):id(a),dist(b){}        ));        ;i<=n;i++)dist[i]=inf;        dist[st]=;        ;i= ...

  2. HTTP的报文格式解析

    一.概述 http报文是面向文本的,报文中每一个字段都是一些ASCII码串,各个字段的长度是不确定的.http有两类报文:请求报文  响应报文 二.请求报文 一个http请求报文由 请求行(reque ...

  3. SQL练习之求解填字游戏

    SELECT * FROM dbo.spt_values

  4. C#实现多态之一抽象

    1. 抽象类.抽象方法.抽象属性的特点 (1)      关键字:abstract (2)      抽象类只能是其他类的基类 (3)      抽象成员必须存在于抽象类中,但抽象类可以没有抽象成员, ...

  5. setTimeOut和setInterval详解

    setTimeout和setInterval的语法相同.它们都有两个参数,一个是将要执行的代码字符串,还有一个是以毫秒为单位的时间间隔,当过了那个时间段之后就将执行那段代码.不过这两个函数还是有区别的 ...

  6. java 中有几种方法可以实现一个线程? 用什么关键字修 饰同步方法? stop()和 suspend()方法为何不推荐使用?

    java5 以前, 有如下两种:第一种:new Thread(){}.start();这表示调用 Thread 子类对象的 run 方法, new Thread(){}表示一个Thread 的匿名子类 ...

  7. HiveQL与SQL区别

    转自:http://www.aboutyun.com/thread-7327-1-1.html 1.Hive不支持等值连接 SQL中对两表内联可以写成:select * from dual a,dua ...

  8. JavaScript 网页链接调用Android程序

    如何让网页链接实现启动Android的应用,网上有说重写WebView相关的shouldOverrideUrlLoading方法,但是这种理论上能实现,因为你的网页不是仅仅被你自己的webview来浏 ...

  9. css样式写一个三角形

    <style> .test{ border-color:transparent #abcdef transparent transparent; border-style:solid; b ...

  10. 四轴飞行器1.3 MPU6050(大端)和M4的FPU开启方法

    四轴飞行器1.3 MPU6050(大端)和M4的FPU开启方法  原创文章,欢迎转载,转载请注明出处      最近时间花在最多的地方就是STM32的I2C上了.之前就知道STM32的I2C并不好用, ...