object model-Function 语意学

来源:互联网 发布:java培训课 编辑:程序博客网 时间:2024/05/16 16:15

0.member的各种调用方式

注意本节是介绍class member data的各种访问(调用)方式

a.nonstatic member functions(非静态成员函数)

好吧,个人觉得作者自己是非常懂的,但是他讲出来的时候,感觉真的好混乱。通常都是给一个例子,自己体悟,做总结的那些话,总是不放在最显眼的位置。让人抓不到重点。歪果仁都是这么随性吗,讲到哪儿是哪儿

辨析member function 和nonmember function

class People{private:int step;public:People():step(0){} void getUp(){step++;}void work(){step++;}void goSleep(){step++;}//member fuctionvoid PeopleAction(){getUp();work();goSleep();}};//同等功能的nonmember fuction void PeopleAction(const People *p){p->getUp();p->work();p->goSleep();} 

c++的设计准则之一就是:nonstatic member function 要与一般的nonmember function有相同的效率。

举个小栗子(nostatic member function 其实都是最后被编译器转化成nonmember function的)

nostatic member function

class Point3d{private:double _x;double _y;double _z;public:double magnitude()const{return sqrt(_x*_x+_y*_y+_z*_z);} Point3d normalize()const{double mag=magnitude();Point3d normal;normal._x=_x/mag;normal._y=_y/mag;normal._z=_z/mag;return normal;}};
转化步骤:

1. 改写函数的signature以安插一个额外参数(this指针)到member function中

//第一个const表示函数是const的,第二个const表示this指针是const的 double magnitude( const Point3d * const this)

2.将每一个对nonstatic data member的存取操作改为由this指针存取

{return sqrt(this->_x*this->_x+this->_y*this->_y+this->_z*this->_z);}
3.将member function重新写成一个外部函数,并将函数名称经过magling(所谓的Name Mangling是指在member的名称上加上class名称以及member function的signature)处理,使它成为在程序中独一无二的语汇
extern double magnitude_7Point3dFv(const Point3d *const this);

至此,如下调用均会被编译器转化为
obj. magnitude ();//变为 magnitude_7Point3dFv(&obj);ptr-> magnitude();//变为 magnitude_7Point3dFv(ptr);

同样的,normalize也会被编译器转化成这样:
//假设编译器存在NRV(named return value) void normalize_7Point3dFv(const Point3d *const this, Point3d &_result){double mag=this->magnitude();_result._x=this->_x/mag;_result._y=this->_y/mag;_result._z=this->_z/mag;return;}//然后又被写成externextern  void normalize_7Point3dFv(const Point3d *const this, Point3d &_result);
b.virtual member function(虚拟成员函数)

同样是从以下两个情况来分析

obj. magnitude ();ptr-> magnitude();
对于 ptr -> magnitude( ); 而言,其内部转换为( * ptr -> vptr[ 1 ] )( ptr );,(理由是:指针支持多态,该指针指向的到底是谁的函数还不明确,所以一定得查虚表)其中:

Vptr表示右边一起产生的指针,指向virtual table;1是virtual table slot的索引值,关联到normalize();第二个ptr表示this指针。

对于obj.magnitude( ); 而言,由于只有指针和引用才能支持多态,一般的object并不支持多态,所以调用的magnitude()只能是Point3d 的函数。“经由一个class object 调用一个 virtual function”,这种操作总是会被编译器像对待一般nonstatic member function 一样的加以 resolved:

magnitude_7Point3dFv(&obj);

c.static member function(静态成员函数)

如果Point3d::normalize()是一个static memberfunction,以下两个调用:

obj.normalize();ptr->normalize();
将被转换为一般的nonmember函数调用,像这样:
normalize_7Point3dSFv();//SFv表示static member function,拥有一个空白(void)的参数链表(argument list)normalize_7Point3dSFv();
独立于class object之外的存取class object的static data member操作主要有两种方法

1.未引入static member function概念之前,程序上的解决之道是将0强制转换为一个class指针,因而提供一个this指针实例

#include<iostream>using namespace std;class Point3d{private:static int const x=5;public:int object_count(){cout<<x<<endl;return x;}};int main(){((Point3d *)0)->object_count();//5return 0;}
2.再就是引入了static member function的概念,用static member function 去访问static member data

static member function的主要特性就是它没有this指针,所以1.他不能直接存取nonstatic member data;2.它不需要经由class object才能被调动;3.他不能被声明为const、volatile或virtual(这一条中const和volatile不明白)。

再就是由于static member function由于不存在this指针,所以它的地址类型不是一个“ class member function pointer”而是一个“nonmember function pointer”。(this指针是指向隐含的object的,object中仅仅存储了nonstatic member data 和指向虚函数表的vptr。所以访问了nonstatic member data的nonstatic member function需要this指针,访问虚函数表的virtual member function也需要this指针

1.virtual member functions(虚拟成员函数)

Virtual function的一般实现模型(无继承):每一个class有一个virtual table,内含class之中有作用的virtual function的地址,然后每个object有一个vptr,指向virtual table的存在。这一节会讨论单一继承,多重继承和虚拟继承这三种情况。

这是我听到的最好的多态解释:在c++中,多态表示 “ 以一个public base class的指针(或引用),寻址找出一个derived class object” 的意思。父类型指针接受子类型对象

a.单继承下的virtual function

对于单继承的derived class而言,它只有一张virtual table

z() 是虚函数,对于ptr->z();而言,到底调用的是谁的z()?对于这个问题,需要清楚两点1.ptr实际上指的是父类型对象,还是子类型对象?2.在知道是哪个类型的对象的情况下,如何找到正确的函数地址。

对于第一个问题:在(具有虚函数的或者父类有虚函数的)class object(因为没有虚函数,也就玩不了多态,这样的class object无需打上标记)上添加一个member(或许是字符串或者一串数字),用以记录指针所指对象的真实类型。

对于第二个问题:在(具有虚函数的或者父类有虚函数的)class object上添加一个指向虚函数表的vptr指针,用以获取虚函数的地址。当然每个子class的虚函数表是需要继承父虚函数表(就是拷贝,可能还会改写或扩充)。

以下是单一继承的情况:

对于derived class而言

1.它可以继承base class 所声明的virtual function,具体地说,父类的virtual function的实体地址会被拷贝到子类的virtual table中去。

2.它可以拥有自己的virtual function,这表示它自己的virtual function实体地址必须拷贝到自己的virtual table中去。

3.它可以改写父类的virtual function,这表示,该改写后的virtual function的实体地址需要替换原来父类虚函数实体地址在自己的virtual table的位置。

现在如果有这样的式子:ptr->z();。一般而言,每次调用z()时,并不知道ptr所指对象的真正类型(这是指编译器并不知道,而不是程序员并不知道),但是可以知道经由ptr可以存取到该对象的virtual table,可以知道每一个z()函数地址被放在slot 4中。所以编译器可以将该调用转化为:(*ptr->vptr[ 4 ] )( ptr );,在这一转换中,唯一在执行期才能知道的东西是:slot 4所指的到底是哪一个z()函数实例。

b.多重继承下的virtual function

对于多重继承下的derived class而言,它有n-1张virtual table(n为derived class上subobject的个数)

在多重继承中支持virtual function,其复杂度围绕在第二以及后继的base class上 2. “ 必须在执行期调整this指针 ”。

小栗子:

 #include<iostream> using namespace std;  class Base1 { protected: float data_base1;public:Base1(){}virtual ~Base1();virtual void speakClearly();virtual Base1 *clone()const; }; class Base2 { protected: float data_base2;public:Base2(){}virtual ~Base2();virtual void mumble();virtual Base2 *clone()const; }; class Derived:public Base1,public Base2 { protected: float data_derived;public:Derived(){}virtual ~Derived();virtual Derived *clone()const; };

对于第二个问题 “ 必须在执行期调整this指针 ”的引出和解决:

Base2 *pbase2=new Derived; 
新的derived 对象的地址必须要调整,指向Base2 subobject(因为new Derived的地址是指向最左端的Base1 subobject的,而Base1与Base2并无继承关系,且两者均存在clone(),但是又不相同)。以下是编译时期的调整
Derived *temp=new Derived;//创建新对象 Base2 *pbase2=temp?temp+sizeof(Base1):0;//Base1在Base2前,故Base2的subobject在Base1的subobject之后,此句将对象指针指向Base2的subobject

当要删除对象时,又必须将对象地址指回起始处,以求删除整个对象。

delete pbase2;
然而上述的offset加法却不能够在编译时期直接设定,因为pbase2所指的真正对象只有在执行期才能确定。所以必须在执行期调整this指针。

为解决这个问题,一帮子人想了一种办法Thunk技术

所谓thunk是一小段assembly代码,用来(1) 以适当的offset值调整this指针(2)跳到virtual function去。例如,经由一个Base2执行调用Derived destructor,其相关的thunk可能看起来是这个样子:

Pbase2_dtor_thunk:This+=sizeof(base1);Derived::~Derived(this);
Thunk技术允许virtual table slot继续内含一个简单的指针,因此多重继承不需要任何空间上的额外负担。Slots中的地址可以直接指向virtual function,也可以指向一个相关的thunk(如果需要调整this指针的话)。于是,对于那些不需要调整this指针的virtual function而言,也就不需承载效率上的额外负担。
然后就有了下图

未打星号的依旧执行,打了星号的执行thunk(用以调整this指针,并执行虚函数)。

在多重继承下一个derived class内含n-1个额外的virtual table。一如章一《关于对象》中的描述,derived class 与最左端基类共享一张virtual table作为主要表格,其他基类各有一张virtual table作为次要表格。

Base::mumble()打星号是因为this指针必须指向Base2 subobject才能正确调用Base2::mumble方法,而derived::~derived()和derived::clone()打星号是因为,拷贝derived class object和删除derived class object的操作对象必须是整个的derived class object,所以必须调整this指针回到起始处。

c.虚继承下的virtual functions

在虚基类中最好不要声明nonstatic member data。

2.指向member function的指针

取一个非静态成员函数nonstatic member function的地址,如果该函数是nonvirtual,则得到的结果是它在内存中真正的地址。它也需要被绑定于某个class object的地址上,才能够通过它调用该函数。所有的nonstatic member functions都需要对象的地址(以参数this指出)。

使用一个"member function"指针,如果并不用于virtual function, 多重继承,virtual base class等情况的话,并不会比使用一个"nonmember function指针”的成本更高。而virtual function 的出现,会使得"member function指针“更复杂化。

b.指向Virtual Member Functions的指针

float (Point::*pmf)() = &Point::z;Point *ptr = new Point3d;
pmf, 一个指向member function的指针,被设置为Point::z()(一个virtual function)的地址。ptr->z(), 被调用的是Point3d::z()。我们从pmf间接调用z()呢?(ptr->pmf)(); 调用的是Point3d.z()吗?答案是yes。问题时如何实现的呢?


我们知道,对一个非静态数据成员取其地址,将获得该函数在内存中的地址,然而面对一个虚拟函数,其地址在编译时候是未知的,所能知道的仅是virtual function在其相关的virtual table中的索引值。也就是说,对一个virtual member function取其地址,所能获得的只是一个索引值。例如:

class Point{      public:          virtual ~Point();          float x();          float y();          virtual float z();};
&Point::~Point = 1; 取&Point::x()的地址则是内存中的地址。通过pmf来调用z(),会被转化为一个编译时期的式子,一般形式如下所示:
(*ptr->vptr[ (int)pmf ] ) (ptr);

3.Inline Functions

一般而言,处理一个inline函数,有两个阶段:

一:分析函数定义。如果函数因其复杂度,或因其建构问题,被判断不可成为inline,它会被转为一个static函数。

二:真正的inline函数扩展操作是在调用的那一个点上。这会带来参数的求值操作(evaluation)以及临时性对象的管理。
在inline函数的扩展进行处理时,大部分的编译器厂商似乎认为不值得在inline支持技术上做详细的讨论。我们只有进入到汇编代码中,才可以看到是否真正的实现了Inline。

一般而言,inline函数中的每一个局部变量都必须被放在函数调用的一个封闭区段内,拥有一个独一无二的名字。Inline函数中的局部变量(展开处同名),再加上有副作用的参数,可能会导致大量临时性对象的产生。特别是如果它以单一表达式被扩展多次的话。如果一个Inline函数被调用多次的话,会产生大量的扩张码,使程序的大小暴涨。所以,我们需要小心的处理。

0 0
原创粉丝点击