Function语义学(一)
C++支持三种类型的 function:
nonstatic member function
static member function
virtual member funcion
Member 的各种调用方式
-
Nonstatic Member Funcions
非静态成员函数C++的设计准则之一就是:**nonstatic member funcion 至少必须和一般的 nonmember funcion 有相同的效率。**也就是说调用 nonstatic member function 不应该带来额外的负担。
float magnitude3d ( const Point3d *_this ) { ... } float Point3d::magnitude3d () const { ... }// 假设magnitude3d的定义 float magnitude3d ( const Point3d *_this) {return sqrt( _this->_x * _this->_x + _this->_y * _this->_y +_this->_z * _this->_z ); }
直接这样看到话,好像非静态成员函数更加有效率,因为它们省去了寻址的过程。但是,实际上,非静态成员函数在编译器内部就是转化为了非成员函数的定义。我们看看编译器是如何将非静态成员函数进行转化的吧。
-
改写函数签名(signature,书上叫做函数原型,但是我觉得叫函数签名比较好听,因为很多其他书上也是这么叫的),并安插一个额外的参数到成员函数中,用以提供一个管道,使 class object 得以将此函数调用。该额外参数称为 this 指针。(执行期,this指针会自动地跟一个class object 进行绑定)。
Point3d Point3d::magnitude(Point3d *const this) { ... } Point3d Point3d::magnitude(const Point3d *const this) { ... }
-
将每一个 “ 对 nonstatic data member 的存取操作” 改为经由 this 指针来存取。
{return sqrt(this->_x * this->_x +this->_y * this->_y +this->_z * this->_z); }
-
将 member function 重新写成一个外部函数。该函数名经过 “ mangling ” 处理,使它在程序中成为独一无二的语汇:
extern magitude__7Point3dFv (register Point3d *const this);
-
将每一个调用操作进行转换
obj.magintude(); magnitude__7Point3dFv(&obj); ptr->magintude(); magintude__7Point3dFv(ptr);
我们看看当返回值有类型的时候是怎么重写的,其实这在之前的章节中都有提到。
Point3d Point3d::normalize() {register float mag = magnitude();Point3d normal;normal._x = _x/mag;normal._y = _y/mag;normal._z = _z/mag;return normal; }float Point3d::magnitude() const {return sqrt(_x * _x + _y * _y + _z * _z); }// 编译器内部转化 void normalize_7Point3dFv( register const Point3d *const this, Point3d &__result) {register float mag = this->magnitude();// default constructor_result.Point3d::Poind3d();_result._x = this->_x/mag;_result._y = this->_y/mag;_result._z = this->_z/mag;return; }
现在我们看看的书上说的一个比较有效率的方法(但是我没看出来,可能是因为不太了解构造函数吧哈哈,也可能是构造函数里面用到了是初值化列表
initialize table
)Point3d Point3d::normalize() const {register float mag = magnitude();return Point3d(_x/mag, _y/mag, _z/mag); }// 看看编译器是怎么进行内部转换的 void normalize_7Point3dFv( register const Point3d *const this,Point3d &_result) {register float mag = this->magnitude();_result.Point3d::Point3d(_x/mag, _y/mag, _z/mag);return; }
书:这可以节省默认构造函数所引起的负担(有道理)。
-
-
名称的特殊处理 (Name Mangling)
一般而言,member 的名称前面会被加上 class 名称,形成独一无二的命名。
class Bar { public: int ival; ... } // 经过 name mangling 之后 int ival_3Bar;
为什么这么做呢?看个例子就清楚了。
class Foo : public Bar { public: int ival; ...} // 现在在一个Foo类中,有两个同名的变量,编译器需要对它们进行区分 // 我们对不同类变量的区分是这样的 int Foo::ival; int Bar::ival; // 但是编译器往往采用最简单的方法--改名-> name mangling class Foo : public Bar { public:int ival_3Foo;int ival_3Bar; }
众所周知,类成员包括成员变量和成员函数,现在我们就来看看成员函数。
由于函数可以被重载化,所以编译器需要更广泛的 mangliing 手法。
看例子:
class Point { public:void x( float newX);float x();... };// 如果转换为 class Point { public:void x_5Point(float newX);float x_5Point();... }
这样的话就会有两个函数拥有相同的名字,但是一般情况下,也是不会出错的,因为它们的函数签名并不相同。但是为了制造出独一无二的效果,只有将它的参数列表也加进到编码中。
但是如果你声明了
extren C
,就会压抑 nonmember functions 的 “mangling” 效果。 -
virtual menber functions 虚拟成员函数
经过前面的学习,相信大家对 virtual member function 和 virtual function table 都不陌生了吧。但是书上有的该写的还是得写,唉。
// 如果这是个 virtual member function
ptr->normalize();
// 那么在编译器内部就会转化为
(* ptr->vptr[1])(ptr);
- vptr 表示由编译器产生的指针,指向 virtual function table(通过之前的学习,我们知道,可能 virtual function table 中不一定只有virtual function pointer,可能在负索引是 virtual base class 的 offset)。它被安插在每一个“声明有(或继承自)一个或多个 virtual functions ”的 class object 中。事实上, 它的名称也可能被 “mangling” 因为在一个复杂的 class 派生体系中,可能存在多个 vptr。
- 1 表示 virtual table slot 的值(由编译器在编译器决定的,关联到对应的函数。
- 第二个 ptr 表示 this 指针。
现在我们看看之前的例子:如果 normalize()
也是一个 virtual member function
// register float mag = magnitude()
register float mag = (* this->vptr[2])(this);
这个时候,由于Point3d::magnitude()
是在Point3d::noralize()
中被调用的,而后者已经由虚拟机制而决议(resolve)妥当,所以直接显式调用Point3d
实例会更有效率,并因此压制了由于虚拟机制而产生的不必要的重复操作:
register float mag = Point3d::magnitude();
如果magnitude()
声明为 inline 函数,会更有效率。使用clas scope operator
显式调用一个 virtiual function,其决议方式会和 nonstatic member function 一样。
Point3d obj;
obj.normalize();
// change
normalize_7Point3dFv( &obj );
// 并不需要通过 vptr 对 virtual member function 进行访问
书:编译器的这种优化工程的另一个利益是,virtual function 的一个 inline 函数实例可以被扩展开来(expanded),因而提供极大的效率利益。(后面会讲到)
-
Static Member Functions (静态成员函数)
在引入 Static Member Function 之前,C++语言要求所有的 member function 都必须经由该 class 的 object 调用。而实际上,只有当一个或多个 nonstatic data members 在member function 中被直接存取时,才需要 class object。Class object 提供了 this 指针给这种形式的函数调用使用。这个 this 指针把 “在 member function 中存取的 nonstatic class members” 绑定于 “object 内对应的 members ” 之上。如果没有任何一个 members 被直接存取,事实上就不需要 this 指针,因此也就没有必要通过一个 class object 来调用一个 member function。但是C++语言到目前为止并不能辨识这种情况。
这么一来就在存取 static data members 产生了一些不规则性。如果 class 的设计者把 static data member 声明为 nopublic(好习惯),那么他就必须提供一个或多个 member function 来存取该 member。因此,虽然你可以不靠 class object 来存取一个 static member,但是其存取函数必须绑定一个 class object。
所以。。。
下面介绍一个 static member function 的特性:
- 它不能够直接存取其 class 中的 nonstatic members。
- 它不能够被声明为 const,volatile 或 virtual。
- 它不需要经由 class object 才被调用。
下面再看看一些需要注意的小细节
// 先把例子定义好 unsigned int Point::object_count () {return _object_count; }
-
首先,static member function 也是会被 mangling 的。
-
对一个 static member function 取地址,得到的类型是一个 nonmember function pointer,而不是一个 class member function pointer。
auto p = &Point3d::object_count(); // p -> unsigned int (*)() // 而不是 // unsigned int (Point3d::*)()
-
由于缺乏 this 指针,因此差不多等同于 nonmember function。所以,它可以成为一个 callback 函数(回调函数)。