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 指针调整.

时间: 2024-08-18 22:21:03

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

&lt;深入理解C指针&gt;学习笔记和总结 第四章 指针和数组

数组是一个什么玩意: 数组和指针我的理解,有相同之处也有不同之处.因有相同之处,因此一些资料上说,数组和指针本质是相同的.因有不同之处,因此也有一些资料上说,数组和指针是不一样的. 相同之处: 数组名字和指针名字都代表了一个地址. 如:int num[10];num是数组名.函数开辟了一个存储十个整数类型的空间,而num是他们的首地址. int *p; p=(int *)malloc(10*sizeof(int));类似的,p也指向了首地址. 不同之处是,num[10]中的空间位置是在栈中,而

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

4.5 Inline Functions 以下是Point class 的一个加法运算符的可能实现内容: class Point { friend Point operator+(const Point&, const Point&); }; Point operator+(const Point &lhs, const Point &rhs) { Point new_pt; new_pt._x = lhs._x + rhs._x; new_pt._y = lhs._y +

【C++】深度探索C++对象模型读书笔记--Function(The Semantics of Function)

1. Nonstatic member function(非静态成员函数)的调用方式 编译器会将”member 函数实例“转换为对等的”nonmember函数实例“. 对于非静态成员函数 float Point3d::magnitude3d() const{...} 转换步骤如下: 1. 改写函数的signature(意指:函数原型)以安插一个额外的参数到member function中,用以提供一个存取管道,使class object得以将此函数调用.该额外参数被称为this指针: //non

Virtual Member Functions &amp; Static Member Function

如果一个 normalize() 是一个 virtual member function, 那么以下的调用: ptr->normalize(); 将会被内部转化为: (*ptr->vptr[1])(ptr); 其中:vptr 表示由编译器生成的指针, 指向 virtual table, 它被安插在每一个声明有(或继承自) virtual functinos 的 class object 中. 事实上其名称也会被 mangled, 因为在一个复杂的 class 派生体系中, 可能存在多个 vpt

[百度空间] [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 types (3.9.2), and cv-qualified versions of these types (3.9.3) are collectively called scalar types. Scalar types, POD-struct types, POD-union types (c

第四章、function语意学

情况一:非静态成员函数C++的设计准则就是非静态成员函数至少和一般的非静态函数有相同的效率,因此非静态成员函数会被改写:举个例子:float Point3d::magnitude3d()const {return x;}①改写函数原型,添加一个额外的参数this到member function中,用于提供一个存取管道,使得class object得以掉用该函数.float Point3d::magnitude3d(const Point3d *const this) {return x;}如果是

C++对象模型——Virtual Member Functions (虚拟成员函数)(第四章)

4.2 Virtual Member Functions (虚拟成员函数) 已经看过了 virtual function的一般实现模型:每一个 class 有一个 virtual table,内含该 class 中有作用的 virtual function的地址,然后每个object有一个vptr,指向 virtual table的所在. 为了支持 virtual function机制,必须首先能够对多态对象有某种形式的"执行期类型判断法(runtime type resolution)&quo

C++对象模型——Member的各种调用方式(第四章)

第四章 Function语意学 (The Semantics of Function) 如果有一个Point3d的指针和对象: Point3d obj; Point3d *ptr = &obj; 当这样做: obj.normalize(); ptr->normalize(); 时,会发生什么事情呢?其中的Point3d::normalize()定义如下: Point3d Point3d::normalize() const { register float mag = magnitude()

C++ 之 const member function

一个常量成员函数(const member function), 可以读取类的数据成员,但不能修改类的数据成员. 1  声明 在成员函数声明的参数列表后,加上 const 关键字,将其声明为常量成员函数(const member function),表明其不被允许修改类的数据成员 下面定义了一个 Date 类,分别以年.月.日的形式来表示日期 class Date { public: int day() const { return d; } int month() const { return