【C++】深度探索C++对象模型读书笔记--Function语意学(The Semantics of Function)
Member的各种调用方式:
Nonstatic member function(非静态成员函数)的调用方式
编译器会将”member 函数实例“转换为对等的”nonmember函数实例“。
对于非静态成员函数
float Point3d::magnitude3d() const{...}
转换步骤如下:
1. 改写函数的signature(意指:函数原型)以安插一个额外的参数到member function中,用以提供一个存取管道,使class object得以将此函数调用。该额外参数被称为this指针:
//non-const nonstatic member的扩张过程 Point3d Point3d::magnitude(Point3d *const this) 如果是member function 是const,则变成: //const nonstatic member的扩张过程: Point3d Point3d::magnitued(const Point3d *const this)
2. 将每一个”对nonstatic data member的存取操作”改为经由this指针来存取:
3.将member function重新写成一个外部函数。将函数名称经过“mangling”处理,使它在程序中成为独一无二的词汇:
extern magnitude_7Point3dFv( register Point3d *const this);
现在这个函数已经被转换好了,而其每一个调用操作也都必须被转换。于是:
1 obj.magnitude(); 2 变成了: 3 magnitude_7Point3dFv(&obj); 4 而 5 ptr->magnitued(); 6 变成了: 7 magnitude_7Point3dFv(ptr);
Virtual Member Functions(虚拟成员函数)
使用指针调用虚拟函数:
如果normalize()是一个virtual member function,那么以下的调用:
ptr->normalize();
将会被内部转化为:
(*ptr->vptr[1])(ptr);
其中:
- vptr表示由编译器产生的指针,指向virtual table。它被安插在每一个“声明有(或继承自)一个或多个virtual functions”的class object中
- 1是virtual table slot 的索引值,关联到normalize()函数。
- 第二个ptr表示this指针。
使用类对象调用虚拟函数:
使用类对象调用虚拟函数,其解析方式和非静态成员函数一样。对于上一节中的normalize()函数。调用方式最终被解析为:
normalize_7Point3dFv(&obj);
Static member Function(静态成员函数)
1.静态成员函数的主要特性:
- 它不能直接存取class中的nonstatic members
- 它不能被声明为cosnt、volatile或virtual
- 它不需要经由class object才被调用--虽然大部分时候它是这样被调用的
2. 取一个静态成员函数的地址,获得的将是其在内存中的位置,也就是其地址。由于static member function没有this指针,所以其地址类型并不是一个“指向class member function的指针”,而是一个“nonmember函数指针“
Virtual Member Functions(虚拟成员函数)
1. 识别一个class是否支持多态,唯一适当的方法就是看看它是否有任何虚函数,只要类拥有虚函数。只要一个类拥有一个虚函数,那么就可以说该类支持多态。
2. 一个类只会含有一个virtual table。每个table内含其对应之class object中所有的active virtual functions函数实例的地址。这些active virtual functions包括:
1.这一class所定义的函数实例。它会改写一个可能存在的base class virtual function函数实例
2. 继承自base class的函数实例。这是在derived class决定不改写virtual funciton时才会出现的情况。
3. 一个pure_virtual_called()函数实例,它既可以扮演pure virtual function的空间保卫者角色,也可以当作执行期一场处理。
我们定义如下继承体系:
class Point { public: virtual ~Point(); virtual Point& mult(float) = 0; //纯虚函数,调用此函数程序会结束 float x() const {return _x}; //不会被放在virtual table中,因为不是虚函数 virtual float y() const {return 0;} virtual float z() const {return 0;} protected: Point(float x = 0.0); float _x; }; class Point2d: public Point { public: Point2d(float x = 0.0, float y = 0.0) :Point(x), _y(y){} ~Point2d(); //改写base class virtual functions Point2d& mult(float); float y() const {return _y;} protected: float _y; }; class Point3d: public Point2d { public: Point3d(float x = 0.0, float y = 0.0, float z = 0.0) :Point2d(x, y), _z(z){} ~Point3d(); //改写base class virtual functions Point3d& mult(float); float z() const { return _z;} protected: float _z; };
则它们的virtual table的布局是这样的:
多重继承:
我们有如下类继承体系:
class Base1 { public: Base1(); virtual ~Base1(); virtual void speakClearly(); virtual Base1 *clone() const; protected: float data _Base1; }; class Base2 { public: Base2(); virtual ~Base2(); virtual void mumble(); virtual Base2 *clone() const; protected: float data_Base2(); }; class Derived : public Base1, public Base2 { public: Derived(); virtual ~Derived(); virtual Derived *clone() const; protected: float data _Derived; };
在多重继承之下,一个derived class内含n-1个额外的virtual tables, n表示其上一层base classes的个数(因此,单一继承将不会有额外的virtual tables)。对于本例的Derived而言,会有两个virtual tables被编译器产生出来:
1. 一个主要实例,与Base1(最左端base class有关)
2. 一个次要实例,与Base2(第二个base class有关)
针对每一个virtual tables,Derived对象中有对应的vptr。vptrs将在构造函数中被设立初值。(经由编译器所产生出来的代码)。
用以支持“一个class拥有多个virtual tables”的传统方法是,将每个tables以外部对象的形式产生出来,并给予独一无二的名称。例如,Derived所关联的两个tables可能这样的名称:
vtbl_Derived; //主要表格
vtbl_Base2_Derived; //次要表格
于是当你将一个Derived对象地址指定给一个Base1指针或Derived指针时,被处理的virtual table是主要表格vtbl_Derived.而当你将一个Derived对象地址指定一个Base2指针时,被处理的virtual table是次要表格vtbl_Base2_Derived.
Virtual Table布局如下:
注意:两个virtual table中都有Derived类的虚函数地址。