这篇将总结C++的函数语义学相关的内容( Inside The C++ Object Model, Chapter 4 )。
Nonstatic Member Function
非静态成员函数和普通函数的等价形式:
|
|
一般来说,非静态成员函数在经过编译时会被转化为非成员函数,转化步骤如下:
- 改写函数原型,参数变为
this
指针。 - 将所有成员变量的存取操作都转化为经this指针的操作。
- 将此函数重写为一个外部函数并导出,名称进行 Name Mangling。最后函数的调用形式也随之改变:
obj.magnitude()
转化为类似于magnitude_7Point3dFv(&obj)
这种名称
Deep in Virtual Function
单继承下的虚函数
对于普通的类,每个类只含一个虚函数表(vtbl
),其中虚函数表记录了基本信息(type_info
)及各函数的地址。每个对象在编译时都会被安插虚函数指针(vptr
)指向虚函数表。
对于以下类:
|
|
其中的Scala
类和Scalaz
类的模型如下所示:
注意:不同的C++编译器对vtable
的实现不同,vtbl
的初始偏移量可能是0,也可能是-8之类的。如果编译器开启了RTTI,则vtbl
里会包含type_info
。
现在如果调用ptr->g()
,我们并不知道ptr所指对象的具体类型,但是有两点很清楚:
- 无论ptr对应哪种对象,我们总是可以通过ptr找到对应对象的vtable
- 无论ptr对应哪种对象,g函数的地址总是在slot 3位置
因此此调用可以转化为:(*ptr->vptr[3])(ptr)
用gdb查看运行时的vtbl(命令:i vtbl 对象名
):
|
|
纯虚函数为什么等于0
在C++标准中,通过使虚函数=0来定义纯虚函数,其含义是在vtbl对应的地方填上0。关于为什么设计纯虚函数,以及纯虚函数为什么为0, The Design and Evolution of C++ 中的描述是:
The curious =0 syntax was chosen over the obvious alternative of introducing a new keyword pure or abstract because at the time I saw no chance of getting a new keyword accepted. Had I suggested pure, Release 2.0 would have shipped without abstract classes. Given a choice between a nicer syntax and abstract classes, I chose abstract classes. Rather than risking delay and incurring the certain fights over pure, I used the tradition C and C++ convention of using 0 to represent “not there.” The =0 syntax fits with my view that a function body is the initializer for a function also with the (simplistic, but usually adequate) view of the set of virtual functions being implemented as a vector of function pointers.
另外一点:在MSVC中,NULL = 0;而在GCC的实现中,NULL
的内部实现是__null
而不是0。因此在定义纯虚函数的时候不要用NULL代替0,也不能用C++ 11的nullptr
。
普通多继承下的虚函数
假设派生类直接继承了n个类,则派生类中就会有n个vptr
。多继承下的派生类拥有一个主要的vptr
和 n-1 个次要的vptr
。多重继承最左端的基类,在派生类中作为主要实体,其对应的vtbl
为主要的vtbl
。
假设现在有下面的类:
|
|
在上面的继承关系中,Base1
就作为主要实体。
例如,有以下调用:
|
|
这两个指针所指对象对应的vtbl
是不同的,里面涉及多继承指针转换的问题:
b1
不需要调整this
指针(最左边的类)b2
需要调整this
指针
对b2
来说,构造函数必须调整对象地址,使其指向Base2 sub-object(当然析构函数也是):
|
|