非原创,在学习
4 Function语意学( The Semantics of Function )
假如有一个Point3d的指针和对象:
cpp
Point3d obj;
Point3d* ptr = &obj;
这样做
cpp
obj.normalize();
ptr->normalize();
时,会发生什么事?其中的Point3d::normalize()定义如下:
cpp
Point3d
Point3d::normalize() const
{
// magnitude是一个函数,magnitude (Read Only)返回向量的长度,
// 也就是点P(x,y,z)到原点(0,0,0)的距离
register float mag = magnitude();
Point3d normal;
normal._x = _x / mag;
normal._y = _y / mag;
normal._z = _z / mag;
return normal;
}
其中Point3d::magnitude ()又定义如下:
cpp
float Point3d::magnitude() const
{
return sqrt(_x * _x + _y * _y + _z * _z);
}
答案是:不知道!C++支持三种类型的 member functions**: static、nonstatic和virtual**,每一种类型被调用的方式都不相同。其间差异正是下一节的主题。不过,我们虽然不能够确定normalize()和magnitude()两函数是否为virtual或nonvirtual,但可以确定它一定不是 static,原因有二:(1)它直接存取nonstatic数据;(2)它被声明为const。是的,static mermber functions不可能做到这两点。
4.1 Member 的各种调用方式
virtual函数是20世纪80年代中期被加进来的;
Static member functions是最后被引人的一种函数类型。它们在1987年的Usenix C++研讨会的厂商研习营(Implementor's Workshop)中被正式提议加人C++中,并由 cfront 2.0实现出来.
Nonstatic Member Functions(非静态成员函数)
C++的设计准则之一就是:nonstatic member function 至少必须和一般的nonmember function 有相同的效率。也就是说,如果我们要在以下两个函数之间作选择:
cpp
float magnitude3d(const Point3d* _this) { }
float Point3d::magnitude3d() const { }
那么选择member function不应该带来什么额外负担。这是因为编译器内部已将"member函数实体"转换为对等的"nonmember函数实体"。
举例:
cpp
float magnitude3d(const Point3d* _this) {
return sqrt(_this->_x * _this->_x +
_this->_y * _this->_y +
_this->_z * _this->_z);
}
乍见之下似乎nonmember function比较没有效率,它间接地经由参数取用坐标成员,而member function 却是直接取用坐标成员.**然而实际上 member function被内化为nonmember的形式。**下面就是转化步骤:
1 改写函数的signature以安插一个额外的参数到member function中,用以提供一个存取管道,是class object得以调用该函数,该额外参数被称为this指针:
cpp
// non-const nonstatic member之增长过程
Point3d
Point3d::magnitude(Point3d* const this)
如果member function是const,则变成:
cpp
// const nonstatic member之扩张过程
Point3d
Point3d::magnitude(const Point3d* const this)
就是加形参:
(1)如果是普通(非静态、非const)成员函数,加Point3d* const this这个参数
(2)如果是const成员函数,加const Point3d* const this这个参数
2 将每一个"对nonstatic data member的存取操作"改为经由this指针来存取:
cpp
{
return sqrt(_this->_x * _this->_x +
_this->_y * _this->_y +
_this->_z * _this->_z);
}
3.将member function重新写成一个外部函数.对函数名称进行"mangling"处理,使它在程序中成为独一无二的语汇:
Name Mangling 是一种在编译过程中,将函数、变量的名称重新改编的机制
cpp
extern magnitude__7Point3dFv(
register Point3d *const this);
现在这个函数已经被转换好了,而其每一个调用操作也都必须转换。于是:
cpp
obj.magnitude();
变成了
cpp
magnitude__7Point3dFv(&obj);
而
cpp
ptr->magnitude();
变成了
cpp
magnitude__7Point3dFv(ptr);
本章一开始所提及的normalize()函数会被转化为下面的形式,其中假设已经声明有一个Point3d copy constructor,而named returned value (NRV)的优化也已施行:
cpp
// C++伪码
void
normalize__7Point3dFv(register const Point3d* const this,
Point3d& _result)
{
// magnitude是一个函数,magnitude (Read Only)返回向量的长度,
// 也就是点P(x,y,z)到原点(0,0,0)的距离
register float mag = this->magnitude();
_result.Point3d Point3d();
_result._x = this->_x / mag;
_result._y = this->_y / mag;
_result._z = this->_z / mag;
return ;
}
一个比较有效率的做法是直接建构"normal"值,像这样:
cpp
Point3d
Point3d::normalize() const
{
register float mag = magnitude();
return Point3d(_x / mag, _y / mag, _z / mag);
}
它会被转化为以下的码(我再一次假设Point3d的copy constructor已经声明好了,而NRV的优化也已实施):
cpp
// C++伪码
void
normalize__7Point3dFv (register const Point3d* const this,
Point3d& _result)
{
register float mag = this->magnitude();
// _result用以取代返回值
_result.Point3d::Point3d(this->_x / mag, this->_y / mag, this->_z / mag);
return;
}
这可以节省default constructor初始化所引起的额外负担。
名称的特殊处理( Name Mangling )
由于member functions可以被重载化 ( overloaded),所以需要更广泛的mangling手法,以提供绝对独一无二的名称。
为了让它们独一无二,唯有再加上它们的参数链表(可以从函数原型中参考得到)。如果把参数类型也编码进去,就一定可以制造出独一无二的结果,使我们的两个x()函数有良好的转换。
Virtual Member Functions(虚拟成员函数)
如果normalize()是一个virtualmember function,那么以下的调用:
cpp
ptr->normalize();
将会被内部转化为:
cpp
(*ptr->vptr[1])(ptr);
其中:
- vptr表示由编译器产生的指针,指向 virtual table.它被安插在每一个"声明有(或继承自)一个或多个virtual functions"的class object中。事实上其名称也会被"mangled",因为在一个复杂的 class 派生体系中可能存在有多个vptrs.
- 1是virtual table slot的索引值,关联到normalize()函数
- 第二个ptr表示this指针.
类似的道理,如果magnitude()也是一个virtual function,它在normalize()之中的调用操作将被转换如下:
cpp
// register float mag = magnitude();
register float mag = (*this->vptr[2])(this);
此时,由于Point3d::magnitude()是在 Point3d:.normalize()中被调用,而后者已经由虚拟机制而决议((resolved)妥当,所以明确地调用"Point3d实体"会比较有效率,并因此压制由于虚拟机制而产生的不必要的重复调用操作:
cpp
// 明确的调用操作会压制虚拟机机制
register float mag = Point3d::magnitude();
如果magnitude(声明为inline函数会更有效率。使用class scope operator明确调用一个virtual function,其决议(resolved)方式会和nonstatic member function一样:
cpp
register float mag = magnitude__7Point3dFv(this);
对于以下调用:
cpp
// Point3d obj;
obj.normalize();
如果编译器把它转换为:
cpp
(*obj.vptr[1])(&obj);
虽然语意正确,却没有必要。请回忆那些并不支持多态( polymorphism)的对象( 1.3节)。所以上述经由obj 调用的函数实体只可以是 Point3d::normalize() ,"经由一个 class object调用一-个virtual function",这种操作应该总是被编译器像对待一般的nonstatic member function 一样地加以决议(resolved) :
cpp
normalize__7Point3dFv(&obj);
这项优化工程的另一利益是,virtual function的一个inline函数实体可以被扩展( expanded)开来,因而提供极大的效率利益。
Static Member Functions(静态成员函数)
如果 Point3d::normalize()是一个static member function,以下两个调用操作:
cpp
obj.normalize();
ptr->normalize();
将被转换为一般的nonmember函数调用,像这样:
cpp
// obj.normalize();
normalize()__7Point3dSFv();
// ptr->normalize();
normalize()__7Point3dSFv();