C++对象模型——指向Member Function的指针 (Pointer-to-Member Functions)(第四章)
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)(第四章)的更多相关文章
- C++对象模型——Inline Functions(第四章)
4.5 Inline Functions 以下是Point class 的一个加法运算符的可能实现内容: class Point { friend Point operator+(const Poin ...
- Function语义学之member function
之前我们讲过编译器会对 nonmember functions 进行怎样的扩充和该写,今天我们来讲一下 member functions 函数调用方式 一.Nonstatic Member Funct ...
- 《深度探索C++对象模型》笔记——Function语意学
member的各种调用方式 C++支持三种类型的member functions:static.nonstatic和virtual. nonstatic member functions会被编译器转换 ...
- 【C++对象模型】第四章 Function 语意学
1.Member的各种调用方式 1.1 Nonstatic Member Functions 实际上编译器是将member function被内化为nonmember的形式,经过下面转化步骤: 1.给 ...
- [百度空间] [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 ...
- 指针函数(Pointer Function)和函数指针(Pointer to Function或Function Pointer)
一.指针函数 1.解释:指针函数很好理解:简单来说,就是一个返回指针的函数,本质是一个函数.如: int fun(int x,int y); //这是一个普通函数的声明,返回值是一个int类型, ...
- 你好,C++(15)四两拨千斤——3.9 指向内存位置的指针
3.9 指向内存位置的指针 一天,两个变量在街上遇到了: “老兄,你家住哪儿啊?改天找你玩儿去.” “哦,我家在静态存储区的0x0049A024号,你家呢?” “我家在动态存储区的0x0022FF0 ...
- C++ - 模板类模板成员函数(member function template)隐式处理(implicit)变化
模板类模板成员函数(member function template)隐式处理(implicit)变化 本文地址: http://blog.csdn.net/caroline_wendy/articl ...
- 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: ...
随机推荐
- CAD参数绘制mcdbsolid对象(com接口)
C#中实现代码说明: private void DrawSolid() { //绘McDbSolid对象 axMxDrawX1.AddLinetype("MLineType1", ...
- freenas 系统可能存在的bug
1.portal 中ip端口显示有问题. 2.创建extend/target映射之后重启iscsi服务有的时候不能启动. 3.后台/usr /etc 重启系统会自动还原.
- 02Document Type Definition
Document Type Definition 1. Document Type Definition DTD(Document Type Definition)文件格式定义作用是给予文件一种格(T ...
- Lua表(table)的用法_个人总结
Lua表(table)的用法_个人总结 1.表的创建及表的介绍 --table 是lua的一种数据结构用来帮助我们创建不同的数据类型.如:数组和字典--lua table 使用关联型数组,你可以用任意 ...
- Bzoj 3307 雨天的尾巴(线段树合并+树上差分)
C. 雨天的尾巴 题目描述 N个点,形成一个树状结构.有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最多的是哪种物品. 输入格式 第 ...
- 初次使用IDEA创建maven项目
第一次使用IDEA,创建一个maven项目,首先下载maven,官方地址:http://maven.apache.org/download.cgi 解压,在环境变量里配置 path里 D:\maven ...
- 设计方案--移动端延迟300ms的原因以及解决方案
一.前言 移动端浏览器提供一个特殊的功能:双击(double tap)缩放. 二.移动端延迟300ms的原因 为什么要用触摸事件?触摸事件是移动端浏览器特有的html5事件. 因为移动端的clic ...
- cadence中元件所在库
DISCRETE(分立元件)中 开关: 其中可供选择的这几个比较好 SW PUSHBUTTON SW PUSHBUTTON-DPST 数码管: LDD(开头) LTD(开头) 版权声明:本文为博主原创 ...
- 解决EF 4.0 中数据缓存机制
EF4.0默认开启缓存机制,如果想要禁用缓存机制的话,则须加上一句话:_db.CreateObjectSet().MergeOption = MergeOption.OverwriteChanges; ...
- HDU 2442
状态压缩DP , 和HDU2280极其相似 #include <cstdio> #include <cstring> #include <iostream> usi ...