4.4 指向Member Function的指针 (Pointer-to-Member Functions)

取一个nonstatic data member的地址,得到的结果是该member在 class 布局中的byte位置(再加1),它是一个不完整的值,须要被绑定于某个 class object的地址上,才可以被存取.

取一个nonstatic member function的地址,假设该函数是nonvirtual,则得到的结果是它在内存中真正的地址.然而这个值也是不全然的,它也须要被绑定与某个 class object的地址上,才可以通过它调用该函数,全部的nonstatic member functions都须要对象的地址(以參数 this 指出).

一个指向member function的指针,其声明语法例如以下:

double		// return type
(Point::* // class the function is member
pmf) // name of the pointer to member
(); // argument list

然后能够这样定义并初始化该指针:

double (Point::*coord)() = &Point::x;

也能够这样指定值:

coord = &Point::y;

想调用它,能够这样做:

(origin.*coord)();

(ptr->*coord)();

这些操作会被编译器转化为:

(coord)(&origin);

(coord)(ptr);

指向member function的指针的声明语法,以及指向"member selection运算符"的指针,其作用是作为 this 指针的空间保留者.这这也就是为什么 static member function(没有 this
指针)的类型是"函数指针",而不是"指向member function的指针"的原因.


使用一个"member function指针",假设并不用于 virtual function,多重继承,virtual base class 等情况的话,并不会比使用一个"nonmember function指针"的成本更高.上述三种情况对于"member function指针"的类型以及调用都太过复杂.其实,对于那些没有 virtual functions或 virtual base class,或multiple
base class 的 class 而言,编译器能够为它们提供同样的效率.下一节讨论为什么 virtual function的出现,会使得"member function指针"更复杂化.

支持"指向Virtual Member Functions"的指针

注意以下的程序片段:

float (Point::*pmf)() = &Point::z;
Point *ptr = new Point3d;

pmf,一个指向member function的指针,被设值为Point::z()(一个 virtual function)的地址,ptr则被指定以一个Point3d对象,假设直接经由ptr调用z():

ptr->z();

则被调用的是point3d::z(),但假设从pmf间接调用z()呢?

(ptr->pmf)();

仍然是Point3d::z()被调用吗?也就是说,虚拟机制仍然可以在使用"指向member function的指针"的情况下运行吗?答案是yes,问题是怎样实现呢?

对一个nonstatic member function取其地址,将获得该函数在内存中的地址,然而面对一个 virtual function,其地址在编译时期是未知的,所能直到的仅是 virtual function在其相关的 virtual table中的索引值.也就是说,对一个 virtual member function取其地址,所能获得的仅仅是一个索引值.

比如,如果有下面的Point声明:

class Point {
public:
virtual ~Point();
float x();
float y();
virtual float z();
};

然而取得destructor的地址:

&Point::~Point;

得到的结果是1,取x()或y()的地址:

&Point::x();
&Point::y();

得到的则是函数在内存中的地址,由于它们不是 virtual,取z()的地址:

&Point::z();

得到的结果是2,通过pmf来调用z(),会被内部转化为一个编译时期的式子,一般形式例如以下:

(*ptr->vptr[(int)pmf])(ptr);

对一个"指向member function的指针"评估求值(evaluted),会由于该值有两种意义而复杂化;其调用操作也将有别于常规调用操作.pmf的内部定义,也就是:

float (Point::*pmf)();

必须同意该函数可以寻址出nonvirtual x()和 virtual z()两个member functions,而那两个函数有着同样的原型:

// 二者都能够被指定给pmf
float Point::x() { return _x; }
float Point::z() { return 0; }

仅仅只是当中一个代表内存地址,还有一个代表 virtual table中的索引值.因此,编译器必须定义pmf使它能够(1)还有两种数值,(2)更重要的是其数值能够被差别代表内存地址还是 virtual table中的索引值.

在多重继承下,指向Member Functions的指针

为了让指向member functions的指针也可以支持多重继承和虚拟继承,Stroustrup设计了以下一个结构体:

// 一般结构,用以支持在多重继承下指向member functions的指针
struct __mptr {
int delta;
int index;
union {
ptrtofunc faddr;
int v_offset;
};
};

它们要表现什么呢?index和faddr分别(不同一时候)带有 virtual table索引和nonvirtual member function地址.在该模型下,像这种调用操作:

(ptr->*pmf)();

会变成:

(pmf.index < 0)
? // non-virtual invocation
(*pmf.faddr)(ptr)
: // virtual invocation
(*ptr->vptr[pmf.index](ptr));

这样的方法所受到的批评是,每个调用操作都得付出上述成本,检查其是否为 virtual 或 nonvirtual.Microsoft把这项检查拿掉,导入一个它所谓的vcall thunk.在此策略下,faddr被指定的要不就是真正的member function地址(假设函数是nonvirtual的话),要不就是vcall thunk的地址.于是 virtual 或novirtual函数调用操作透明化,vcall
thunk会选出并调用相关 virtual table 中的适当slot.

这个结构体的还有一个副作用是,当传递一个不变的值的指针给member function时,它须要产生一个暂时性对象.举个样例,假设这样做:

extern Point3d foo(const Point3ed&, Point3d(Point3d::*)());
void bar(const Point3d& p) {
Point3d pt = foo(p, &Point3d::normal);
}

当中&Point3d::normal的值类似这样:

{0, -1, 10727417}

将须要产生一个暂时性对象,有明白的初值:

__mptr temp = {0, -1, 10727417}
foo(p, temp);

本节一開始的那个结构体,delta字段表示 this 指针的offset值.而v_offset字段放的是一个 virtual(或多重继承中的第二或后继的)base class 的vptr位置.假设vptr被编译器放在 class 对象的起始处,这个字段就没有必要了,代价则是C对象兼容性减少.这些字段仅仅在多重继承或虚拟继承的情况下才有其必要性,有很多编译器在自身内部依据不同的 class 特性提供多种指向member
functions的指针形式,比如Microsoft就供应了三种风格:

1. 一个单一继承实例(当中带有vcall thunk地址或是函数地址)

2. 一个多重继承实例(当中带有faddr和delta两个members)

3. 一个虚拟继承实例(当中带有四个members)

"指向Member Functions的指针"的效率

在以下一组測试中,cross_product()函数经由以下方式调用:

1. 一个指向 nonmember function 的指针

2. 一个指向 class member function 的指针

3. 一个指向 virtual member function 的指针

4. 多重继承下的 nonvirtual 以及 virtual member function call

5. 虚拟继承下的 nonvirtual 以及 virtual member function call.



第一个測试(指向 nonmember function 的指针)下面列方式进行:

Point3d* (*pf)(const Point3d&, const Point3d &) = cross_product;
for (int iters = 0; iters < 10000000; iters++)
(*pf)(pA, pB);
return 0;

第二个測试(指向 member function 的指针)的声明和调用操作例如以下:

Point3d* (Point3d::*pmf)(const Point3d &) const = &Point3d::cross_product;
for (int iters = 0; iters < 10000000; iters++)
(pA.*pmf)(pB);

上述操作会被转化为下面的内部形式,于是下面的函数调用:

(pA.*pmf)(pB);

会被转化为这种推断:

pmf.index < 0
? (*pmf.faddr)(&pA + pmf.delta, pB)
: (*pA.__vptr__Point3d[pmf.index].faddr)
(&pA + pA.__vptr__Point3d[pmf.index].delta, pB);

一个"指向member function的指针"是一个结构,内含三个字段:index,faddr和delta.index若不是内带一个相关 virtual table的索引值,就是以-1表示函数是 nonvirtual.faddr带有nonvirtual member function的地址.delta带有一个可能的 this 指针调整.

C++对象模型——指向Member Function的指针 (Pointer-to-Member Functions)(第四章)的更多相关文章

  1. C++对象模型——Inline Functions(第四章)

    4.5 Inline Functions 以下是Point class 的一个加法运算符的可能实现内容: class Point { friend Point operator+(const Poin ...

  2. Function语义学之member function

    之前我们讲过编译器会对 nonmember functions 进行怎样的扩充和该写,今天我们来讲一下 member functions 函数调用方式 一.Nonstatic Member Funct ...

  3. 《深度探索C++对象模型》笔记——Function语意学

    member的各种调用方式 C++支持三种类型的member functions:static.nonstatic和virtual. nonstatic member functions会被编译器转换 ...

  4. 【C++对象模型】第四章 Function 语意学

    1.Member的各种调用方式 1.1 Nonstatic Member Functions 实际上编译器是将member function被内化为nonmember的形式,经过下面转化步骤: 1.给 ...

  5. [百度空间] [note] pointer to member is a POD type

    C++03 3.9-10: 1 Arithmetic types (3.9.1), enumeration types, pointer types, and pointer to member ty ...

  6. 指针函数(Pointer Function)和函数指针(Pointer to Function或Function Pointer)

    一.指针函数 1.解释:指针函数很好理解:简单来说,就是一个返回指针的函数,本质是一个函数.如: int fun(int x,int y);    //这是一个普通函数的声明,返回值是一个int类型, ...

  7. 你好,C++(15)四两拨千斤——3.9 指向内存位置的指针

    3.9  指向内存位置的指针 一天,两个变量在街上遇到了: “老兄,你家住哪儿啊?改天找你玩儿去.” “哦,我家在静态存储区的0x0049A024号,你家呢?” “我家在动态存储区的0x0022FF0 ...

  8. C++ - 模板类模板成员函数(member function template)隐式处理(implicit)变化

    模板类模板成员函数(member function template)隐式处理(implicit)变化 本文地址: http://blog.csdn.net/caroline_wendy/articl ...

  9. leetcode 编译问题:Line x: member access within null pointer of type 'struct TreeNode'

    参考: LEETCODE 中的member access within null pointer of type 'struct ListNode' 解决 leetcode 编译问题:Line x: ...

随机推荐

  1. 【leetcode-03】给定一个字符串,请你找出其中不含有重复字符的最长子串的长度

    开个新坑,leetcode上面做题目.下面是题目描述: <!-- 给定一个字符串,请你找出其中不含有重复字符的最长子串的长度. 示例 1: 输入: "abcabcbb" 输出 ...

  2. org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [********] will not be managed by Spring

    如下图,查看层次是否正确.

  3. OpenCV2:应用篇 QT+OpenCV实现图片编辑器

    一.简介 做完会放在Github上  

  4. nginx配置实现负载均衡

    一.负载均衡的作用 1.转发功能 按照一定的算法[权重.轮询],将客户端请求转发到不同应用服务器上,减轻单个服务器压力,提高系统并发量. 2.故障移除 通过心跳检测的方式,判断应用服务器当前是否可以正 ...

  5. 11-3 re模块

    目录 r 的作用 re模块的常用功能 findall search match split sub 将数字替换成'H' subn 将数字替换成'H',返回元组(替换的结果,替换了多少次) compil ...

  6. 执行jar包报错:udfFull.jar中没有主清单属性

    在windows系统的cmd命令行窗口中执行: java -jar udfFull.jar {"movie":"1287","rate":& ...

  7. find_in_set()和in()比较

    转载于:https://www.cnblogs.com/zqifa/p/mysql-4.html 作者:zqifa 因为自己太懒了,就从大佬那转载来,作为一次笔记! mysql 中find_in_se ...

  8. 关于Integer,127和128的问题

    里面的,直接贴源码来看 Integer i=127; Integer b=128; Integer c=128; Integer d=127;Integer j;System.out.println( ...

  9. CEO的作用

    看到有人讨论CEO的作用. 一个观点认为CEO有三大任务: 1)为公司确定战略,并与股东沟通 2)为公司其他职位找来合适的人员 3)保证公司随时有足够的钱 他认为,可能CEO会有其他的作用,但是这三点 ...

  10. Android BGABadgeView:BGABadgeImageView以及BGABadgeRelativeLayout(4)

     Android BGABadgeView:BGABadgeImageView以及BGABadgeRelativeLayout(4) 在附录文章5,6,7的基础上,写一个小例子说明BGABadge ...