用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。

  1. class Point{
  2. public:
  3. Point(float xval);
  4. virtual ~Point();
  5. float x()const;
  6. static int PointCount();
  7. protected:
  8. virtual ostream& print(ostream &os) const;
  9. float _x;
  10. static int _point_count;
  11. };

在C++对象模型中,non-static 数据成员被配置于每一个对象内static 数据成员被存放在类对象之外静态和非静态成员函数也被放在类对象之外

Virtual 函数以两个步骤支持:

  1. 每一个 class 产生出一堆指向 virtual functions 的指针,放在 virtual table(vtbl) 中
  2. 每一个 class object 都有一个虚指针(vptr),指向virtual table。该指针的设定和重置都经由构造、析构和拷贝赋值运算符自动完成每一个 class 关联的 type_info object(用于支持RTTI,运行时类型判定)也是由 vptr 指出的。 vptr 指向哪一个 vtbl 是运行期设定的。

image.png


  1. class istream:virtual public ios{...};
  2. class ostream:virtual public ios{...};

image.png
在虚继承中,无论基类被派生多少次,永远只有一个实例。e.g. iostream中只有ios的一个实例。


一个派生类塑模其基类

基类 table 被创造出来时,表格中的每一个 slot 内含一个相关的基类地址,每一个 class object 都内含一个 bptr,指向 base class table
image.png

1.2 关键词所带来的差异

struct 在C++中就是接口全为 public class

struct 在C++中的合理用途是:当要传递一个复杂的 class object 的全部或部分到某个 C函数去时,struct可以将数据封装起来,并保证和 C兼容的空间布局。 但是必须是组合而不是继承。(即让 struct 作为 class的数据成员)

1.3 对象的差异

C++通过下列方法支持多态:

  1. 通过隐式类型转换,将一个 derived class 指针转化为指向 public base type 的指针:

    1. shape *ps = new circle();
  2. 通过虚函数机制

    1. ps->rotate();
  3. 通过dynamic_casttypeid运算符:

    1. if(circle *pc = dynamic_cast<circle*>(ps))

    多态主要由一个共同的定义在 base class 中接口来影响类型的封装,这个共享接口是以 virtual function 机制引发的,可以在运行期根据 object 的真实类型解析调用函数


一般而言,表现一个 class object 需要的大小包括:

  • 非静态数据成员的总和大小
  • 加上任何对齐带来的开销
  • 加上为了支持 virtual 机制 而由内部产生的额外负担
  1. class ZooAnimal{
  2. public:
  3. ZooAnimal();
  4. virtual ~ZooAnimal();
  5. virtual void rotate();
  6. protected:
  7. int loc;
  8. std::string name;
  9. };
  10. ZooAnimal za("Zoey");
  11. ZooAnimal *pza=&za;

此时对象的内存布局可能如图所示:
image.png

加上多态之后

  1. class Bear:public ZooAnimal{
  2. public:
  3. Bear();
  4. ~Bear();
  5. void rotate();
  6. virtual void dance();
  7. protected:
  8. enum Dances{};
  9. Dances dances_known;
  10. int cell_block;
  11. };
  12. Bear b("Yogi");
  13. Bear *pb = &b;
  14. Bear &rb = *pb;

可能的内存布局:
image.png

  1. Bear b;
  2. ZooAnimal *pz = &b;
  3. Bear *pb = &b;

它们每个都指向 Bear object 的第一个 byte ,但是 pb 涵盖整个 Bear object ,而 pz 只包含 Bear object 中的 ZooAnimal subobject 那一部分。

除了Base subobject中出现的成员,不能使用动态绑定的指针访问派生类的任何成员,例外是 virtual 函数。e.g. 此处不能用 pz 去访问 Bear object 部分中的dances_knowncell_block

而当我们使用pz->rotate()时,pz 所指的对象类型可以决定rotate()调用的实体,类型信息的封装不在pz中,而是在 link 中,该 link 存在于对象的 vptr 和 vptr 所指的 vtbl 之间。


当一个基类对象被直接初始化为一个派生类对象时,derived object 就会被切割,以塞入较小的 base type 内存中,多态不再出现。