用C语言和C++编写代码实现Point3d
,C++增加了封装性,也并没有带来布局成本的增加:
- data members 直接内含在类对象中,就像 C struct 一样。
- member function 虽然在 class 声明中,却不出现在 object 中。每一个 non-inline member function只会诞生一个函数实例;然而 inline member function 会在每一个使用者上产生一个函数实例。
Hint:只要在类内定义的 member function 就是自动 inline 的。
C++布局和存取时间上的额外负担主要来自于 virtual :
- virtual function 机制:用于支持一个有效率的“运行期绑定”;
- virtual base class 机制:用于实现“多次出现在继承体系中的 base class,有一个单一而被共享的实例”。
1.1 C++对象模式
C++中有两种 class 数据成员:static 和 non static;
以及三种class 成员函数:static、non static 和 virtual。
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;
};
在C++对象模型中,non-static 数据成员被配置于每一个对象内,static 数据成员被存放在类对象之外。静态和非静态成员函数也被放在类对象之外。
Virtual 函数以两个步骤支持:
- 每一个 class 产生出一堆指向 virtual functions 的指针,放在 virtual table(vtbl) 中
- 每一个 class object 都有一个虚指针(vptr),指向virtual table。该指针的设定和重置都经由构造、析构和拷贝赋值运算符自动完成。每一个 class 关联的 type_info object(用于支持RTTI,运行时类型判定)也是由 vptr 指出的。 vptr 指向哪一个 vtbl 是运行期设定的。
class istream:virtual public ios{...};
class ostream:virtual public ios{...};
在虚继承中,无论基类被派生多少次,永远只有一个实例。e.g. iostream中只有ios的一个实例。
一个派生类塑模其基类
基类 table 被创造出来时,表格中的每一个 slot 内含一个相关的基类地址,每一个 class object 都内含一个 bptr,指向 base class table。
1.2 关键词所带来的差异
struct 在C++中就是接口全为 public 的 class 。
struct 在C++中的合理用途是:当要传递一个复杂的 class object 的全部或部分到某个 C函数去时,struct可以将数据封装起来,并保证和 C兼容的空间布局。 但是必须是组合而不是继承。(即让 struct 作为 class的数据成员)
1.3 对象的差异
C++通过下列方法支持多态:
通过隐式类型转换,将一个 derived class 指针转化为指向 public base type 的指针:
shape *ps = new circle();
通过虚函数机制:
ps->rotate();
通过
dynamic_cast
和typeid
运算符:if(circle *pc = dynamic_cast<circle*>(ps))
多态主要由一个共同的定义在 base class 中接口来影响类型的封装,这个共享接口是以 virtual function 机制引发的,可以在运行期根据 object 的真实类型解析调用函数。
一般而言,表现一个 class object 需要的大小包括:
- 非静态数据成员的总和大小
- 加上任何对齐带来的开销
- 加上为了支持 virtual 机制 而由内部产生的额外负担
class ZooAnimal{
public:
ZooAnimal();
virtual ~ZooAnimal();
virtual void rotate();
protected:
int loc;
std::string name;
};
ZooAnimal za("Zoey");
ZooAnimal *pza=&za;
此时对象的内存布局可能如图所示:
加上多态之后:
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;
可能的内存布局:
Bear b;
ZooAnimal *pz = &b;
Bear *pb = &b;
它们每个都指向 Bear object 的第一个 byte ,但是 pb 涵盖整个 Bear object ,而 pz 只包含 Bear object 中的 ZooAnimal subobject 那一部分。
除了Base subobject中出现的成员,不能使用动态绑定的指针访问派生类的任何成员,例外是 virtual 函数。e.g. 此处不能用 pz 去访问 Bear object 部分中的dances_known
和cell_block
。
而当我们使用pz->rotate()
时,pz 所指的对象类型可以决定rotate()
调用的实体,类型信息的封装不在pz中,而是在 link 中,该 link 存在于对象的 vptr 和 vptr 所指的 vtbl 之间。
当一个基类对象被直接初始化为一个派生类对象时,derived object 就会被切割,以塞入较小的 base type 内存中,多态不再出现。