深度探索C++对象模型【第一章 】

版权声明:本文为博主原创文章,转载请注明出处-- https://blog.csdn.net/qq_38790716/article/details/84755250

1. C++对象模式

1.在C语言中,“数据”和“处理数据的操作”是分开来声明的,语言本身并没有支持“数据和函数”之间的关联性

2.上述这种程序性的程序方法,由一组“分布在各个以功能为导向的函数中”的算法所驱动,它们处理的是共同的外部数据

3.在C++中,使用ADT(abstract data type,抽象数据类型)来实现函数与数据的封装

4.加上封装后的布局成本(布局以及存取时间上的成本):

  • 1.数据和普通的成员函数并不会带来额外的成本,数据成员被直接内含在每一个类对象中,而成员函数虽然含在类的声明之中,却不出现在对象之中,每一个non-inline成员函数只会诞生一个函数实例,inline function则会在其每一个使用者身上产生一个函数实例
  • 2.主要额外负担由virtual引起,virtual function机制:用以支持一个有效率的“执行期绑定”;virtual base class:用以实现“多次出现在继承体系中的base class,有一个单一而被共享的实例”

5.在C++中,有两种class data members:static和nonstatic,以及三种class member functions:static、nonstatic和virtual

6.对象模型:

  1. 简单对象模型:members按照其声明顺序,各被指定一个slot(插槽);members本身并不放在object之中,只有“指向member的数据成员”才放在object中
  2. 表格驱动对象模型:class object内含指向两个表格的指针,一个指针指向data member table(数据成员表,存放数据),另一个指针指向Member function table(成员函数表,存放成员函数)
  3. C++对象模型:从简单对象模型派生而来,并对内存时间和存取时间做了优化

7.C++对象模型:

  • nonstatic data members被配置于,每一个class object内
  • static data members被存放在个别的class object之外
  • static和nonstatic function members被存放在个别的class object之外
  • virtual functions则以 以下两个步骤支持

1. 每一个class产生出一堆指向virtual functions的指针,放在vtbl(virtual table)表格之中
2. 每一个class object被安插一个指针(vptr),指向相关的virtual table,其中与每一个class关联的type_info object(用以支持RTTI)也经由vtbl指出,放在表格的第一个slot内

关于RTTI:C++ primer第十九章学习

该模型的优缺点:

  • 优点:空间与存取时间的效率高
  • 缺点:如果应用程序代码本身未曾改变,但所用到的类对象的非静态数据成员有所修改,那么应用程序代码都得重新编译

8.在虚拟继承下,基类不管在继承串中被派生多少次,永远只会存在一个实例(subobject)

9.加上继承之后派生类如何在本质上构造其基类的实例?

  1. 在简单对象模型中: 每一个base class可以被derived class object内的一个slot指出。
    优点:class object 的大小不会因其base class的改变而受到影响
    缺点:因为间接性而导致的空间和存取时间上的额外负担

  2. base table模型:base table中的每一个slot内含一个相关的base class 地址,每一个类对象内含一个bptr,会被初始化,指向base class table。
    优点:在每一个类对象中对于继承都有一致的表现方式;无须改变类对象本身,就可以放大、缩小或更改base class table
    缺点:由于间接性而导致的空间和存取时间上的额外负担

  3. C++采用的继承模型:base class subobject的数据成员被直接放置于派生类对象中
    优点:提供了对基类成员最紧凑且最有效率的存取
    缺点:base class members的任何改变,所有用到该base class 或derived class’s objects都需要重新编译

10.不同的对象模型会导致现有的程序代码在内部转换结果不同,例:

//class X定义了一个copy construct、一个virtual destructor、一个virtual function foo
X foobar() {
	X xx;
	X *px = new X;
	//foo()是一个虚函数
	xx.foo();
	px->foo();

	delete px;
	return xx;
}

对于上述程序可能在内部被转换为:

//可能的内部转换结果
//虚拟C++代码
void foobar(X &_result)
{
	//构造_result
	_result.X::X();
	//扩展X *px = new X;
	px = _new(sizeof(X));
	if (px != 0)
		px->X::X();
	//扩展xx.foo()但不使用virtual机制
	foo(&_result);
	//使用virtual机制扩展px->foo()
	(*px->vtbl[2])(px)    
	//扩展delete px
	if (px != 0) {
		( *px->vtbl[1])(px);
		_delete(px);
	}
	return;
}

对于上述函数,可以用之前了解的C++对象模型来解释,由于虚函数指针,即vptr都存放在虚表中,且自顶向下存储,因此在虚表中0号位存储的是指向X类的type_info,1号位存储的是指向X的析构函数(~X())的vptr,2号位存储的是指向虚函数foo()的指针,因此在采用virtual机制扩展的时候对应取函数在虚表中的位置

在这里插入图片描述

2. 关键词所带来的差异

1.struct与class:

  • class和struct“一致性的用法”,只是风格上的问题
  • 引入class, 目的是得到其所支持的封装和继承特性
  • struct默认为pubilc, class默认为private
  • 保留它们是为了维护与c的兼容性

2.C++中凡处于同一个access section的数据,必定保证以其声明顺序出现在内存布局中;被放置在多个access sections中的数据,排列顺序就不一定了

class stumble {
public:
	// operations....
protected:
	// protected stuff...
private:
	/* private stuff */
	char pc[1];
};
//例如上述,public中的数据在内存布局中的顺序与protected与private就不一定相同

3.组合是把C和C++组合在一起的唯一可行方法,struct声明能够将数据封装起来,并保证拥有与C兼容的空间布局

3. 对象的差异

1.C++程序设计模型支持三种programming paradigms(程序设计范式):

  • 程序模型:无类
  • ADT(抽象数据类型模型):有类,无多态
  • OO模型(面向对象模型):有类、有多态

2.只有通过pointer或reference的间接处理,才支持OO程序设计所需的多态性质

3.在C++,多态只存在于一个个的public class体系中。public的派生行为可以直接进行多态的转换;nonpublic的派生行为以及类型为void*的指针可以说是多态的,但并没有被语言明确支持,需要程序员通过调试的转换操作进行管理

4.C++以下列三种方式支持多态:

  • 经由隐式转换操作,例如由派生类指针转换为一个指向其基类的指针
  • 经由虚函数机制
  • 经由dynamic_cast和typeid运算符

5.一个class object的内存大小大概由三部分组成:

  • 其nonstatic data members的总和大小
  • 由于alignment的需求而填补上去的空间
  • 支持virtual所产生的额外负担

6.一个指针,不管指向的是何种数据类型,其本身所需的内存大小都是固定的;那我们应该如何来区分一个void*和一个类指针呢?

这两者,以内存需求看来,并没有什么不同!但“指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小。

就那上面的例子来说吧,一个类指针指向类的对象,将横跨一个地址空间,改地址空间表示该类的内容;而一个void*指针只能持有一个地址,不能通过它操作所指之object的缘故

7.cast是一种编译器指令,大部分情况下它并不改变一个指针所含的真正地址,它只影响“被指出之内存的大小和其内容”的解释方式

8.用一个派生类对象去初始化其基类对象则该派生类对象的特有部分会被切割,只留下基类部分

猜你喜欢

转载自blog.csdn.net/qq_38790716/article/details/84755250