C++成员函数

来源:互联网 发布:nginx和 zookeeper对比 编辑:程序博客网 时间:2024/05/29 04:23

“The Semantics of Function”,本篇的架构很简单,说的是member functions在nonstatic,static和virtual三种状态下的调用方式。

首先来一个开胃菜:假设类Point3d有data members x, y, z,有member function如下,

floatPoint3d::magnitude() const{    return sqrt(x*x + y*y + z*z);}
问你能从这个函数看出什么?

当然,我们无法确定它是virtual还是nonvirtual,但可以确定它一定不是static,因为,

  • static function无法直接存取nonstatic数据;
  • static function不能声明为const。

(1)Nonstatic member function的调用方式
C++的设计准则之一就是:nonstatic member function至少必须和一般的nonmember function有相同的效率。
也就是说,如果有如下两个函数,
float magnitude3d ( const Point3d *_this ) { ... };float Point3d :: magnitude3d const () { ... };
选择member function不会带来任何额外负担。为什么呢?
因为编译器内部已经将“member function实例”转换成了“nonmember function实例”,member function转换过程如下:
  • 1 改写函数signature(也就是改写函数原型);
安插一个额外参数(隐式的this指针)到member function中,它的作用是使class object可以通过它来存取数据。比如对non-const成员与const 成员的扩张如下,
// non-const nonstatic member function augmentationPoint3dPoint3d::magnitude( Point3d *const this )//const nonstatic member augmentationPoint3dPoint3d::magnitude( const Point3d *const this )
ps:
还记得const放在函数声明后面是什么意思吗?
it means that function is not allowed to change any class members(except ones that are marked "mutable").
const放在前面呢?
const before an argument in a functiondefinition means the same as const for a variable, the value is not allowed to change.
具体的请看:[http://stackoverflow.com/questions/3141087/what-is-meant-with-const-at-end-of-function-declaration]
既然说了那么多题外话,那也就提一下突破const限制的mutable C++ 关键字吧!
mutable与const相反,是“可变的”,它永远处于一个可变状态,即使身在一个const function中。
  • 2 对nonstatic data member的存取改为经由this指针来操作
{return sqrt( this->x * this->x + this->y * this->y + this->z * this->z );}
  • 3 通过“name mangling”,将member function重写成一个外部函数
name mangling的作用,是将member的函数名和参数编码到一起,形成一个独一无二的命名。
于是,函数名称变成了
extern magnitude__7Point3dFv ( register Point3d *const this );
那么,
obj.magnitude();ptr->magnitude();
经过改写之后,变成了
magnitude__7Point3dFv( &obj );magnitude__7Point3dFv( ptr );
可见,没有增加任何额外负担。


(2)static member function的调用方式
有人会问,为什么会需要一个static的成员函数,我们来分析一下原因:
我们从上面得知,所有对nonstatic data member的存取都需要借助object来进行,this指针把“member function中要存取的nonstatic data member”和“object内对应的members”绑定在一起。然而,如果存取static data member,因为它在class之外,所以就不需要this指针,也不需要绑定类内数据成员。但我们需要对各种类型的data members的存取方式做出统一,这样一来,就需要一个member function,使它可以绑定在class object上(但的确不需要this指针)。
由于static member function没有this指针的特点,随之而来,它衍生出了几条其它特征:
  • 不能存取class中的nonstatic members;
  • 不能被声明为const, volatile或virtual;
  • 不需要经过class object才被调用——虽然大部分时候需要通过object。
那我们就来看,编译器如何转换一个static member function:
unsigned intPoint3d::object_count (){return _object_count;}
会被转化成:
unsigned intobject_count_5Point3dSFv (){return _object_count_5Point3d;}
这里,SFv表示static member function, void argument list。与nonstatic member function的转化比起来,可以清晰的看到,this指针已经不再存在,不需要object便可以直接访问static data member。
所以,当我们写下如下代码时,
& Point3d::object_count();
我们得到的是nonmember function的函数指针,类型是,
unsigned int (*) ();
而不是得到一个指向class member function的指针,它的指针是 unsigned int ( Point3d::* ) ();类型的。

所以,正因为static member function缺乏this指针,因此差不多等同于nonmember function;而这个意想不到好处是,可以成为一个callback函数,使得我们可以将C++和C-based C Window系统结合,也使得更成功的用在线程函数上。


(3)virtual member function的调用方式
在这一节,我们分单一继承、多重继承、虚拟继承三个方面来探究这个模型。

1 单一继承
Point3d obj;Point3d *ptr = &obj;ptr->z();
看这个代码,z是virtual function;对于ptr->z()的多态特性(也就是virtual function机制),我们知道它是执行期的特征,我们必须要解决的两个问题是:
  • a. ptr是个什么对象?
  • b. 从哪里获取z()?
要回答这两个问题,我们必须添加额外的执行期信息来确定,并且,如果一个class拥有virtual function,那么我们就需要这个额外信息,如果没有,那么在编译时期,我们就可以办妥一切事情,执行时也就不需要额外的附加信息了。

首先来看第一个问题,ptr是个什么对象?
很简单,它的信息我们在执行期才可以得到。

第二个问题,编译时期如何找到z()?
要回答这个问题,我们先来看一段代码:
class Point {public:virtual ~Point ();// slot 1virtual Point& mult( float ) = 0;// pure virtual function, slot 2// ...float x() const { return x; }virtual float y() const { return 0; }//slot 3virtual float z() const { return 0; }//slot 4protected:Point( float x = 0.0 );float _x;}
我们已经知道,virtual functions存放在virtual function中,由class object中的vptr指向。安插vptr和放置virtual functions,当然是编译时期要做的事;执行期,便是要激活virtual table slot中的virtual function。我们看下图:
我们会发现,在继承过程中,需函数对应slot的索引值不会改变,上述代码中4个slot的索引已经在程序中标出,所以,我们在编译时期可以清楚的知道,要调用的z()函数都放在slot 4中,因此编译器可将调用转化为:
( *ptr->vptr[ 4 ] )( ptr );
这样,在单一继承下,virtual function机制便可以很好的运行起来了。

2 多重继承
这个有点麻烦了,因为子类必须维护多个virtual table,看下图:

这种情况下,编译器将负担给了“第二个或后继的base class”,通过在执行期调整this指针,来调用子类中的虚函数。
对如上继承关系,编译器将支持virtual function的困难度放在了Base2 subonject身上,它需要解决3个问题:
  • virtual destructor;
Base1 *pbase1 = new Derived();Base2 *pbase2 = new Derived();// ...delete pbase1;delete pbase2;
如上代码中,对于pbase1的delete,由于Base1是第一个base class,所以不需要调整this指针;而pbase2需要调整。这里面涉及了trunk技术用来以适当的offset调整this指针完成virtual function的跳跃,具体见书上p163;
  • 被继承下来的Base2::mumble()
  • clone();(图中~clone应改为clone)
以上两种情况同样需要指针的调整,不细说了;

3 虚拟继承
我放个图在这里,用来大致描述一下virtual function调用的机制。由于译者对这里也有疑惑,所以想要深究的同学应该去查阅其他资料或论文。

0 0
原创粉丝点击