非原创,在学习
目录
1.1 C++对象模式(The C++ Object Model)
表格驱动对象模型(A Table-driven Object Model)
对象模型如何影响程序(How the Object Model Effects Programs )
1.2 关键词所带来的差异(A Keyword Distinction )
策略性正确的struct(The Politically Correct Struct)
1.3 对象的差异(An Object Distinction)
1 关于对象(Object Lessons)
这里最开始从C语言的结构体引出C++中的”抽象数据类型(ADT)“。
而加上封装之后,布局成本没有增加,三个data member直接内含在每一个class object之中,就像C struct的情况一样,而member functions虽然含在class的声明之内,却不出现在object 之中。每一个non-inline member function只会诞生一个函数实体。至于每一个“拥有零个或一个定义”的inline function 则会在其每一个使用者(模块)身上产生一个函数实体Point3d支持封装性质,这一点并未带给它任何空间或执行期的不良回应。你即将看到,C++在布局以及存取时间上主要的额外负担是由virtual 引起,包括:
- virtual function机制用以支持一个有效率的“执行期绑定”( runtime binding) .
- virtual base class用以实现“多次出现在继承体系中的 baseclass,有一个单一而被共享的实体”。
这里主要在说C++的内存结构。
1.1 C++对象模式(The C++ Object Model)
在c++中有两种成员变量:静态的数据成员(static)、非静态的数据成员(nonstatic);
有三种成员函数:静态的函数(static)、非静态的函数(nonstatic)、虚函数(virtual)
#include <iostream>
using std::ostream;
class Point {
public:
Point(float xval); // 构造函数
virtual ~Point(); // 虚析构函数
float x() const; // 非静态函数(只读)
static int PointCount(); // 静态函数
protected:
virtual ostream& print(ostream& os) const; // 虚函数(只读)
float _x; // 非静态变量
static int _point_count; // 静态变量
} ;
int main(int argc, char** argv) {
system("pause");
return 0;
}
简单对象模型(A Simple Object Model)
在这个简单模型中,一个object是一系列的slots,每一个slot指向一个members。Members按其声明次序,各被指定一个slot。每一个data member或function member都有自己的一个slot,如图所示:
虽然这个模型并没有被应用于实际产品上,不过关于索引或slot数目的观念,倒是被应用到C++的“指向成员的指针”( pointer-to-member)观念之中。
表格驱动对象模型(A Table-driven Object Model)
为了对所有classes 的所有objects都有一致的表达方式,另一种对象模型是把所有与members相关的信息抽出来,放在一个data member table和一个member function table 之中,class object本身则内含指向这两个表格的指针。Member function table是一系列的 slots,每一个slot 指出一个member function;Data member table 则直接含有 data本身,如图所示:
虽然这个模型也没有实际应用于真正的 C++编译器身上,但member function table这个观念却成为支持virtual functions的一个有效方案。
C++对象模型(The C++ Object Model)
在此模型中,Nonstatic datamembers被配置于每一个class object之内,static data members则被存放在所有的 class object之外,Static和 nonstatic function members也被放在所有的 classo bject之外.Virtual functions 则以两个步骤支持之:
1.每一个class产生出一堆指向virtual functions的指针,放在表格之中.这个表格被称为virtual table ( vtbl) .
2.每一个class objcct被添加了一个指针,指向相关的 virtual table。通常这个指针被称为vptr,vptr的设定(setting)和重置(resetting)都由每一个cisss的 constructor、destructor和copy assignment运算符自动完成(在第5章讨论)。每个class所关联的 type_info object(用以支持runtime type identification,RTTI)也经由virtual table被指出来,通常是放在表格的第一个slot处。
上图明C++对象模型如何应用于前面所说的 Point class身上.这个模型的主要优点在于它的空间和存取时间的效率;主要缺点则是,如果应用程序代码本身未曾改变,但所用到的 class objects 的nonstatic data members有所修改(可能是增加、移除或更改),那么那些应用程序代码同样得重新编译.关于这点,前述的双表格模型就提供了较大的弹性,因为它多提供了一层间接性,不过它也因此付出空间和执行效率两方面的代价就是了。
加上继承
C++支持单一继承和多重继承,也支撑虚拟继承。在虚拟继承的情况下,基类不管在继承链中被派生多少次,永远只会存在一个实体。
一个派生类如何在本质上模塑其基类的实体呢?
在”简单对象模型中“,每一个基类可以被派生类对象内的一个slot指出,该slot内含基类实体的地址。这个体制的主要缺点是,因为间接性而导致空间和存取时间上的额外负担,优点则是类对象的大小不会因其基类的改变而受到影响。
没理解优点
当然啦,你也可以想象另一种所谓的 base table模型。这里所说的 base class table被产生出来时,表格中的每一个slot内含一个相关的 base class地址,这很像virtual table 内含每一个virtual function 的地址一样.每一个class object内含一个bptr,它会被初始化,指向其base class table。这种策略的主要缺点是由于间接性而导致的空间和存取时间上的额外负担,优点则是在每一个class object中对于继承都有一致的表现方式:每一个class object都应该在某个固定位置上安放一个base table 指针,与base classes的大小或数目无关.第二个优点是,不需要改变class objects本身,就可以放大、缩小、或更改 base class table。
对象模型如何影响程序(How the Object Model Effects Programs )
看一个例子,X类中定义了一个拷贝构造函数、一个虚析构函数、一个虚函数foo,在此基础上有一个函数foobar
X foobar() {
X xx;
X* px = new X;
// foo是一个虚函数
xx.foo();
px->foo();
delete px;
return xx;
}
这个函数有可能在内部被转化为:
// 可能的内部转化结果
// 虚拟C++码
void foobar(X& _result) {
// 构造_result
// _reslut用来取代local xx...
_reslut.X::X();
// 扩展
px = _new(sizeof(X));
if (px != 0) {
px->X::X();
}
// 扩展xx.foo()但不使用virtual机制
// 以_reslut取代xx
foo(&_result);
// 使用virtual机制扩展px->foo()
(*px->vtal[2])(px)
// 扩展delete
if (px != 0) {
(*px->vtbl)[1](px); // 析构函数
_delete (px);
}
// 不需要使用named return statement
// 不需要摧毁local object xx
return xx;
}
一张图解释上述代码
其实还是C++对象模型,但是这里更具体的说明了虚函数表的第0个位置指向X的type_info object,虚函数表的第1个位置指向虚析构函数,虚函数表的第2个位置指向X::foo()函数。
1.2 关键词所带来的差异(A Keyword Distinction )
为了维护与C语言之间的兼容性,重载函数的解决方式变得很复杂
int (*pf) (1024); // 函数指针的调用
以及在C++中什么时候使用struct代替class
关键字的困扰
关于C++中的struct和class的区别
struct的默认访问修饰符是public;而class的默认访问修饰符是private。
struct的默认继承方式是public,而class的默认继承方式是private。除此之外使用时没有区别。
策略性正确的struct(The Politically Correct Struct)
这里讲述的是在C中,struct没有访问修饰符,因此struct中的成员变量是按声明顺序出现的内存中的,而在C++中,class/struct有访问修饰符,因此成员变量未必是按照声明顺序出现在内存中的。
之前没有注意到这点
1.3 对象的差异(An Object Distinction)
1 程序模型:像C语言一样
2 抽象数据类型模式:其运算定义未言明,如String class
3 面向对象模型:抽象、继承
对象的大小:C++类大小详尽讲解_c++类的大小_StudyWinter的博客-CSDN博客
指针的类型
一个指向复合类型的指针与一个指向简单类型的指针有何不同呢?
以内存需求的观点来说,没有什么不同!它们三个都需要有足够的内存来放置一个机器地址(通常是个word,译注)﹒“指向不同类型之各指针”间的差异,既不在其指针表示法不同,也不在其内容(代表一个地址)不同,而是在其所寻址出来的 object类型不同。也就是说,“指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小:
1、一个指向地址1000 的整数指针,在32位机器上,将涵盖地址空间1000~1003(译注:因为32位机器上的整数是4-bytes) .
2.如果 String是传统的 8-bytes (包括一个4-bytes的字符指针和一个用来表示字符串长度的整数),那么一个ZooAnimal指针将横跨地址空间1000~1015(译注:4+8+4,如图1.4)
ZooAnimal的代码:
class ZooAnimal {
public:
ZooAnimal();
virtual ~ZooAnimal();
// ...
virtual void rotate();
protected:
int loc;
String name;
};
所以,转型( cast)其实是一种编译器指令。大部分情况下它并不改变一个指针所含的真正地址,它只影响“被指出之内存的大小和其内容”的解释方式.
指针间的转型其实是得到其起始位置和所占内存的大小。
加上多态之后(Adding Polymorphism)
定义一个Bear,作为一种ZooAnimal
// ZooAnimal类
class ZooAnimal {
public:
ZooAnimal();
virtual ~ZooAnimal();
// ...
virtual void rotate();
protected:
int loc;
String name;
};
// Bear类
class Bear: public ZooAnimal {
public:
Bear();
~Bear();
// ...
void rotate();
virtual void dance();
// ...
protected:
enum Dances {};
Dances dances_known;
int cell_block;
};
// 调用
Bear b("Yogi");
Bear* pb = &b;
Bear& rb = *pb;
b、pb、rb 会有怎样的内存需求呢?下图展示可能的内存布局
假设Bear object放在地址1000 处,一个Bear指针和一个ZooAnimal指针有什么不同?
// 调用
Bear b;
ZooAnimal* pz = &b;
Bear& pb = &b;
它们每个都指向Bear object的第一个byte,其间的差别是,pb 所涵盖的地址包含整个Bear object,而 pz所涵盖的地址只包含Bear object中的 ZooAnimal subobject.
下面这段代码不难理解
// 不合法
pz->cell_block;
// 合法
((Bear*)pz)->cell_block;
// 更好
if (Bear* pb2 = dynamic_cast<Bear*>(pz)) {
pb2->cell_block;
}
// 合法
pb->cell_block;
这段也不难理解
pz->rotate(); // 调用ZooAnimal中的函数
Bear b;
ZooAnimal za = b; // 这里会切割
za.rotate(); // 调用ZooAnimal中的函数
可能的内部分布图
当一个base class object被直接初始化为(或是被指定为)一个derived class object时,derived object就会被切割(sliced),以塞入较小的 base type内存中,derived type 将没有留下任何蛛丝马迹.多态于是不再呈现,而一个严格的编译器可以在编译时期解析一个“通过该object而触发的 virtual function调用操作”,因而回避virtual机制.如果virtual function被定义为inline,则更有效率上的大收获。