c++对象

这里探讨下c++对象的实现。

对象模型

和c不同,c是将数据和处理数据的函数分开的(数据流过程决定函数编写,过程式的);而c++把数据和处理数据的方法关联在了一起(对象发起动作,操纵数据)。那么像class一样将数据和方法包含在一起,甚至再用上模板是否会增加布局成本呢?

C++对象模式
在C++中,有两种类成员数据:static和非static,以及三种类成员函数:static、非static和virtual。

在C++对象模型中,非static成员数据被配置于每一个类对象之内,static成员数据则被放在所有类对象之外。static和非static成员函数也被放在所有的类对象之外。

C++在布局以及存取时间上主要的额外负担是由virtual引起,包括
1.虚函数机制(执行时绑定)
2.虚基类。还有一些多重继承下的额外负担。

virtual函数则以两个步骤支持之:
1.每一个类产生出一堆指向virtual函数的指针,放在表格之中。这个表格被称为virtual表。
2.第一个类对象被添加了一个指针,指向相关的virtual表。这个指针的设定和重置都由每一个类的构造函数,析构函数和拷贝赋值运算符自动完成。另外,虚函数表地址的前面设置了一个指向type_info的指针,RTTI(Run Time Type Identification)运行时类型识别是有编译器在编译器生成的特殊类型信息,包括对象继承关系,对象本身的描述,RTTI是为多态而生成的信息,所以只有具有虚函数的对象在会生成。

和c相比,c++的布局成本主要来自于virtual,virtual使得class object需要先找虚表,再由虚表找所在地,增加了耗费。而像nonstatic data menber,跟c中struct完全一样,所以这些并不会增加c++的布局成本(相对于c而言)。

对于C++中的单继承
C++实际模型是:对于一般继承是扩充已有存在的虚函数表;对于虚继承添加一个虚函数表指针。

对于C++中的多继承
1)每一个基类都有自己的虚函数表,基类各有虚表
2)子类的虚函数被放到第一个基类的虚函数表中,子类与第一个父类共用一张虚表
3)内存布局中,基类的排列顺序就是基类的声明顺序
4)重写:每一个基类的虚表中的fun都被重写成子类的fun,这样做就是为了解决不同的基类类型的指针指向同一个子类实例,而能够调用到实际的函数。

对于C++中的多继承中的虚继承
虚继承的子类,有单独的虚函数表,另外也单独保存一份父类的虚函数表。

如何访问成员

数据成员如何访问(直接取址)?
跟实际对象模型相关联,根据对象起始地址+偏移量取得。

函数成员如何访问(间接取址)?
跟实际对象模型相关联,普通函数(nonstatic、static)根据编译、链接的结果直接获取函数地址;如果是虚函数根据对象模型,取出对于虚函数地址,然后在虚函数表中查找函数地址。

多态如何实现?
多态(Polymorphisn)在C++中是通过虚函数实现的。如果类中有虚函数,编译器就会自动生成一个虚函数表,对象中包含一个指向虚函数表的指针。能够实现多态的关键在于:虚函数是允许被派生类重写的,在虚函数表中,派生类函数对覆盖(override)基类函数。除此之外,还必须通过指针或引用调用方法才行,将派生类对象赋给基类对象。

为什么析构函数设为虚函数是必要的?
带有多态性质的基类应该声明一个虚析构函数。如果一个类带有任何虚函数,它就应该拥有一个虚析构函数。

如果析构函数不定义为虚函数,那么派生类就不会重写基类的析构函数,在有多态行为的时候,派生类的析构函数不会被调用到(有内存泄漏的风险!)。
一个类的设计目的不是作为基类使用,或不是为了具备多态性,就不该声明虚析构函数。

引用

本质上一个引用通常是以一个指针来实现,而且是const指针。

参考

《深度探索C++对象模型》


La esclavitud no se abolió, se cambió a 8 hrs diarias.