深入探索C++对象模型(六) Function语义学

1. 非虚成员函数指针(非静态)。

取一个非静态成员函数的地址,如果该函数是非虚函数,则得到的是它在内存中的真正地址,但是该地址并不完全,需要绑定与具体的类实例(对象)之上,借助对象的地址(this指针)才可以被调用,例如:一个成员函数指针

[cpp]  view plain  copy
  1. double (Point::* pmf)();  
经过初始化:
[cpp]  view plain  copy
  1. double (Point::*coord)() = &Point::getX;//或者这样初始化它:coord = &Point::getX;  
这样来调用它:


[cpp]  view plain  copy
  1. (orgin.*coord)();//或者这样(ptr->*coord)();  
调用会转化成:
[cpp]  view plain  copy
  1. (coord)(&origin);//或者(coord)(ptr);  
静态成员函数由于没有this指针,所以指向它的指针是函数指针,而不是指向成员函数的指针。

2. 指向虚成员函数的指针。

当一个函数指针指向一个虚成员函数时,例如:

[cpp]  view plain  copy
  1. float (Point::*pmvf)() = &Point::z;//z为虚函数  
  2. Point *ptr = new Point3d;//Point3d为Point的子类  
那么,当ptr通过该函数指针调用z时,多态机制仍会生效,也就是如下调用,调用的仍是Point3d的z()函数。
[cpp]  view plain  copy
  1. (ptr->*pmvf)();//效果等同于ptr->z();  
这是因为,取去函数的地址,,得到的是其在虚表中的索引值,也就是对于如下类:
[cp]  view plain  copy
  1. class Point {  
  2. public:  
  3.     virtual ~ Point();  
  4.     float x();  
  5.     float y();  
  6.     virtual float z();  
  7. };  
&Point::~Point得到的结果是1, &Point::x和&Point::y得到的是它们在内存中的地址,因为它们并非虚函数,而&Point::z结果为2,因为它位于虚表的第三个位置(索引从0开始),所以通过上面例子中的pmvf调用函数,会转化为:


[cpp]  view plai  copy
  1. (*ptr->vptr[(int)pvmf])(ptr);//调用Point3d::z()  
3. 多重继承下,指向成员函数的指针。

由于多重继承(包括多重虚拟继承)涉及到,子类中可能存在多个虚表,this指针的可能需要调整偏移,书中举例了cfront的实现方法,引入一个结构体,在需要的时候保存上述内容,结构体如下:

[cpp]  view plain  copy
  1. struct __mptr {  
  2.     int delta;//虚基类或者多重继承下的第二个以及之后基类的this指针偏移  
  3.     int index;//虚函数索引,非虚函数此值为-1  
  4.     union {  
  5.         ptrtofunc faddr;//非虚函数地址  
  6.         int v_offset;//虚基类或者多重继承下的第二个以及之后基类的虚表位置  
  7.     };  
  8. };  
在此模型下,以下调用:
[cpp]  view plain  copy
  1. (ptr->*pmvf)();  
会变成:


[cpp]  view plain  copy
  1. (pmvf.index < 0) ?  
  2. (*pmvf.faddr)(ptr)//非虚函数  
  3. : (*ptr->vptr[pmvf.index])(ptr);//虚函数  

1. 对于单一表达式的多重调用:

对如下incline函数:

[cpp]  view plain  copy
  1. inline Point operator+ (const Point& lhs, const Point& rhs)  
  2. {  
  3.     Point new_pt;  
  4.     new_pt.x(lhs.x() + rhs.x());//x()函数为成员变量_x的get,set函数  
  5.     return new_pt;  
  6. }  
由于该函数只是一个表达式,在cfront中,则其第二或者后继的调用操作不会被扩展,会变成:
[cpp]  view plain  copy
  1. new_pt.x = lhs._x + x__5PointFV(&rhs);  
没有带来效率上的提升,需要重写为:

[cpp]  view plain  copy
  1. new_pt.x(lhs._x + rhs._x);  
2. inline函数对于形式(Formal)参数的处理。

有如下inline函数:

[cpp]  view plain  copy
  1. inline int min(int i, int j)  
  2. {  
  3.     return i < j ? i : j;  
  4. }  
如下三个调用:
[cpp]  view plain  copy
  1. inline int bar()  
  2. {  
  3.     int minVal;  
  4.     int val1 = 1024;  
  5.     int val2 = 2048;  
  6.   
  7.     minVal = min(val1, val2);//case 1  
  8.     minVal = min(1024, 2048);//case 2  
  9.     minVal = min(foo(), bar() + 1);//case 3  
  10.       
  11.     return minVal;  
  12. }  
对于case1,调用会直接展开:
[cpp]  view plain  copy
  1. minVal = val1 < val2 ? val1 : val2;  
case2直接使用常量:

[cpp]  view plain  copy
  1. minVal = 1024;  
对于case3,由于引发了参数的副作用,需要导入临时对象,以避免重复求值(注意下面逗号表达式的使用):
[cpp]  view plain  copy
  1. int t1;  
  2. int t2;  
  3.   
  4. minVal = (t1 = foo()), (t2 = bar() + 1), t1 < t2 ? t1: t2;  
3. inline函数中引入了局部变量。
改写上述min函数,引入一个局部变量:
[cpp]  view plain  copy
  1. inline int min(int i, int j)  
  2. {  
  3.     int minVal = i < j ? i : j;  
  4.     return minVal;  
  5. }  
对于如下调用:
[cpp]  view plain  copy
  1. int minVal;  
  2. int val1 = 1024;  
  3. int val2 = 2048;  
  4. minVal = min(val1, val2);  
为了维护局部变量,会被扩展为:
[cpp]  view plain  copy
  1. int minVal;  
  2. int val1 = 1024;  
  3. int val2 = 2048;  
  4.   
  5. int __min_lv_minVal;//将inline函数局部变量mangling  
  6. minVal = (__min_lv_minVal = val1 < val2 ? val1: val2), __min_lv_minVal;  

再复杂一些的情况,例如局部变量加上有副作用的参数,会导致大量临时性对象的产生:

[cpp]  view plain  copy
  1. minVal = min(val1, val2) + min(foo(), bar() + 1);  

会被扩展成为,注意逗号表达式,由左至右计算各个分式,以最右端的分式值作为最终值传回:

[cpp]  view plain  copy
  1. int __min_lv_minVal_00;  
  2. int __min_lv_minVal_01;  
  3.   
  4. int t1;  
  5. int t2;  
  6.   
  7. minVal = (__min_lv_minVal_00 = val1 < val2 ? val1 : val2, __min_lv_minVal_00) +  
  8. (__min_lv_minVal_01 = (t1 = foo(), t2 = bar() + 1, t1 < t2 ? t1 : t2), __min_lv_minVal_01);  
会产生多个临时变量,所以inline函数的使用必须要谨慎。

猜你喜欢

转载自blog.csdn.net/coolwriter/article/details/80557783