纯虚函数

可以静态地调用纯虚函数,不能经过虚拟机制。

  1. Abstract_base::pure_function(); //合法调用

HINT:纯虚析构函数必须定义,因为每一个派生类对象在析构的时候,都会自下而上地调用继承链中的每一个基类的析构函数。

如果只声明而不定义纯虚析构函数,那么将会出现链接错误undefined reference to ...
所以替代方案:不要将虚析构函数声明为pure。

5.1 无继承情况下的对象构造

在C中,未初始化的全局对象一般被视为临时性定义,因为没有显式初始化操作,存储在 BSS 段。

C++不支持临时性定义,C++所有全局对象都被以初始化过的数据来对待,所以C++代码中的 BSS 段并不重要

抽象数据类型

观念上类会合成并调用默认拷贝构造、拷贝赋值和析构,但是实际上,在某些符合位逐次拷贝的类中,它们都是 trivial 的,根本没有产生。

继承

虚函数的引入,会导致代码膨胀:

  • 我们自己定义的构造函数被附加了代码,将 vptr 初始化。

image.png

  • 合成一个拷贝构造和一个拷贝赋值运算符,而且其操作不再是 trivial 的。即不再 bitwise ,而是真正产生了调用。编译器会将对象的连续内容拷贝到另一个对象上,而不是精确的“以每个成员为最小单位的颗粒化”。类似于memcpy()

设想,如果一个 Base class 被初始化或以一个派生类赋值,那么 bitwise 方式会带来vptr的非法设定
this->__vptr_Point = __vtbl__Point;

当许多函数都需要传值的方式传回一个局部变量,那么最好提供一个拷贝构造。它的出现会触发NRV优化,NRV优化后将不再需要调用拷贝构造,因为直接在被直接传回的object本体上进行计算


继承体系下的对象构造

当定义一个对象时:T object;构造函数的调用伴随了如下扩充操作:

  • 成员初始化列表中的 data members 的初始化操作会被放到构造函数体内部,并按声明顺序;
  • 如果一个成员没有出现在成员初始化列表中,但是它有一个默认构造函数,则该默认构造函数会被调用;
  • 如果类对象有 vptr,那么必须在构造函数开始处设定初值,指向合适的 vtbl ;
  • 在本体构造之前,所有基类构造函数都要被调用,按照声明顺序。
  1. class Point{
  2. public:
  3. Point(float x = 0.0,float y = 0.0);
  4. Point(const Point&);
  5. Point operator=(const Point&);
  6. virtual ~Point();
  7. virtual float z(){return 0.0;}
  8. protected:
  9. float _x,_y;
  10. };
  11. class Line{
  12. private:
  13. Point _begin,_end;
  14. public:
  15. Line(float = 0.0, float = 0.0, float = 0.0, float = 0.0);
  16. Line(const Point&, const Point&);
  17. };

由于Point声明了拷贝构造、拷贝赋值和析构函数,所以内含Point的类中会合成显式拷贝构造、拷贝复制和析构函数。

虚继承

当出现虚继承:

  1. class Point3d:public virtual Point{
  2. public:
  3. Point3d(float x = 0.0, float y = 0.0, float z = 0.0):
  4. Point(x,y), _z(z){}
  5. Point3d(const Point3d& rhs):
  6. Point(rhs), _z(rhs._z){}
  7. ~Point3d();
  8. Point3d& operator=(const Point3d&);
  9. virtual float z(){return _z;}
  10. private:
  11. float _z;
  12. };

传统的扩充构造函数并不会出现,因为此处是虚继承,共享副本。

试想这一种继承体系:
image.png
Vertex 的构造函数也需要调用 Point 的构造函数,但是当Point3dVertex同为Vertex3d的subobject 时,它们两个不可以调用Point构造函数
转而由底层的Vertex3d完成对Point的初始化;再往后的继承中,则由PVertex来完成被共享的subobject的构造。

为了支持这项特性,需要扩充 constructor ,测试传入的参数并决定是否调用相关的虚基类构造函数:
image.png
Vertex3d的构造函数起始位置判断是否调用虚基类构造函数,并将__most_derived变量设置为false,从而阻断了Point3dVertex对虚基类构造函数的调用。

vptr 初始化语意学

在一个构造函数中经由构造中的对象调用虚函数,其函数实例应该是在此 class 中有作用的那个。
创建一个继承链中最底端的对象,构造函数从上往下调用,在没有调用对应构造函数之前,该class和它底下的部分都没有被创建出来,所以无法调用相应的虚函数。

所以编译器必须保证有函数实例可以被调用,在构造函数中调用的函数以静态方式调用,不介入虚机制Point3d可以调用它作用域内的虚函数。

👆都是扯皮

其实实现起来还是通过虚机制,在 base class 构造函数调用后,在程序员代码之前,设定vptr的值。

5.3 对象复制语意学

不要允许一个虚基类的拷贝操作,或者,不要再任何虚基类中声明数据。