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();
	Point3d normal;
	normal._x = _x / mag;
	normal._y = _y / mag;
	normal._z = _z / mag;

	return normal;
}

而其中的Point3d::magnitude()定义如下:

float Point3d::magnitude() const {
	return sqrt(_x * _x + _y * _y + _z * _z);
}

答案是:不知道.

C++支持三种类型的member functions:static,nonstatic,virtual,每一种类型被调用的方式都不相同.其间差异正是下一节的主题.不过,虽然不能够确定normalize()和magnitude()两函数是否为 virtual 或 nonvirtual,但可以确定它一定不是 static,原因有二:(1)它直接存取nonstatic数据;(2)它被声明为 const,static member functions不可能做到这两点.

4.1 Member的各种调用方式

回顾历史,原始的"C with Classes"只支持nonstatic member functions.Virtual 函数是在20世纪80年代中期被加进来的,并且受到许多质疑.Static member functions是最后被引入的一种函数类型,它们在1987年被正式加入C++中.

Nonstatic Member Functions (非静态成员函数)

C++的设计准则之一就是:nonstatic member functions至少必须和一般的nonmember function有相同的效率.也就是说,如果要在以下两个函数之间作选择:

float magnitude3d(const Point3d *_this) { ...}
float Point3d::magnitude3d() const { ... }

那么选择member function不应该带来什么额外负担,这是因为编译器内部已将"member函数实体"转换为对等的"nonmember函数实体".

举个例子,下面是magnitude()的一个nonmember定义:

float magnitude3d(const Point3d *_this) {
	return sqrt(_this->_x * _this->_x + _this->_y * _this->_y + _this->_z * _this->_z);
}

乍看之下似乎nonmember function比较没有效率,它间接地经由参数取用坐标成员,而member function确实直接取用坐标成员,然而实际上member function被内化为nonmember的形式,下面就是转化步骤:

1.
改写函数的signature(函数原型)以插入一个额外的参数到member function中,用以提供一个存取管道,使 class object得以调用该函数,该额外参数被称为 this 指针:

// non-const nonstatic member的扩张过程
Point3d Point3d::magnitude(Point3d *const this)

如果member function是const,则变成:

// const nonstatic member的扩张过程
Point3d Point3d::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重新写成一个外部函数,对函数名称进行"mangling"处理,使它在程序中成为独一无二的语汇:

extern magnitude__7Point3dFV(register Point3d *const this);

现在这个函数已经被转换好了,而其每一个调用操作也必须转换.于是:

obj.magnitude();

变成了:

magnitude__7Point3dFV(&obj);

ptr->magnitude();

变成了:

magnitude__7Point3dFV(ptr);

本章一开始所提及的normalize()函数会被转化为下面的形式,其中假设已经声明有一个Point3d copy constructor,而named returned value(NRV)的优化也已施行:

// 以下描述"named return value函数"的内部转化
// 使用C++伪代码
void normalize_7Point3dFV(register const Point3d *const this, Point3d &__result) {
	register float mag = this->magnitude();
	// default constructor
	__result.Point3d::Point3d();
	__result._x = this->x / mag;
	__result._y = this->y / mag;
	__result._z = this->z / mag;

	return ;
}

一个比较有效率的做法是直接建构"normal"值,像这样:

Point3d Point3d::normalize() const {
	register float mag = magnitude();
	return Point3d(_x / mag, _y / mag, _z / mag);
}

它会被转化为以下的代码:

// 以下描述内部转化
// 使用C++伪码
void normalize_7Point3dFV(register const Point3d *const this, Point3d &__result) {
	register float mag = this->magnitude();
	// __result用以取代返回值(return value)
	__result.Point3d::Point3d(this->_x / mag, this->_y / mag, this->_z / mag);
	return ;
}

这可以节省default constructor初始化锁引起的额外负担.

名称的特殊处理 (Name Mangling)

一般而言,member的名称前面会被加上 class 名称,形成独一无二的命名.例如下面的声明:

class Bar {
public:
	int ival;
};

其中ival有可能变成这样:

// member经过name-mangling之后的可能结果之一
ival_3Bar

为什么编译器要这样做?请考虑这样的派生操作(derivation):

class Foo : public Bar {
public:
	int iva;
};

记住,Foo对象内部结合了base class 和derived class 两者:

// C++伪码
// Foo的内部描述
class Foo {
public:
	int ival_3Bar;
	int ival_3Foo;
};

不管要处理哪一个ival,通过"name mangling",都可以绝对清楚地指出来,由于member functions可以被重载化(overloaded),所以需要更广泛的mangling手法,以提供绝对独一无二的名称,如果把:

class Point {
public:
	void x(float newX);
	float x();
};

转换为:

class Point {
public:
	void x_5Point(float newX);
	float x_5Point();
};

会导致两个被重载化(overloaded)的函数实体拥有相同的名称,为了让它们独一无二,唯有再加上它们的参数链表(可以从函数原型中参考得到).如果把参数类型也编码进去,就一定可以制造逐独一无二的结果,使两个x()函数有良好的转换:

class Point {
public:
	void x_5PointFf(float newX);
	float x_5PointFv();
}

以上所示的只是cfront采用的编码方法,必须承认,目前的编译器并没有统一的编码方法.

把参数和函数名称编码在一起,编译器于是在不同的被编译模块之间达成了一种有限形式的类型检验,举个例子,如果有一个print函数被这样定义:

void print(const Point3d &) { ... }

但意外地被这样声明和调用:

// 以为是const Point3d &
void print(const Point3d);

两个实体如果拥有独一无二的name mangling,那么任何不正确的调用操作在链接时期就因无法决议(resolved)而失败.有时候可以乐观地称此为"确保类型安全的链接行为"(type-safe linkage)."乐观地"是因为它只可以捕捉函数的标记(signature,即函数名称+参数数目+参数类型)错误;如果"返回类型"声明错误,就没有办法检查出来.

当前的编译系统中,有一种所谓的demangling工具,用来拦截名称并将其转换回去.

Virtual Member Functions (虚拟成员函数)

如果normalize()是一个 virtual member function,那么以下的调用:

ptr->normalize();

将会被内部转化为:

(*ptr->vptr[1])(ptr);

其中:

vptr表示由编译器产生的指针,指向 virtual table,它被插入在每一个"声明有(或继承自)一个或多个 virtual functions"的 class object中.事实上其名称也会被"mangled",因为在一个复杂的 class 派生体系中,可能存在有多个vptrs.

1是 virtual table slot的索引值,关联到normalize()函数

第二个ptr表示 this 指针.

类似的道理,如果magnitude()也是一个 virtual function,它在normalize()中的调用操作被转换如下:

// register float mag = magnitude();
register float mag = (*this->vptr[2])(this);

此时,由于Point3d::magnitude()是在Point3d::normalize()中被调用,而后者已经由虚拟机机制而决议(resolved)妥当,所以明确地调用"Point3d实体"会比较有效率,并因此压制由于虚拟机制而产生的不必要的重复调用操作:

// 明确的调用操作(explicity invocation)会压制虚拟机制
register float mag = Point3d::magnitude();

如果magnitude()声明为 inline 函数会更有效率,使用 class scope operator明确调用一个 virtual function,其决议(resolved)方式会和nonstatic member function一样:

register float mag = magnitude_7Point3dFv(this);

对于以下调用:

// Point3d obj;
obj.normalize();

如果编译器把它转换为:

(* obj.vptr[1])(&obj);

虽然语意正确,却没有必要.请回忆那些并不支持多态(polymorphism)的对象(1.3节),所以上述经由obj调用的函数实体只可以是Point3d::normalize()."经由一个class object调用一个virtual function".这种操作应该总是被编译器像对待一般的nonstatic member function一样加以决议(resolved):

normalize_7PointdFv(&obj);

这样优化工程的另一利益是,virtual function的一个 inline 函数实体可以被扩张(expanded)开了,因而提供极大的效益利益.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-03 22:18:59

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

线程安全的事件调用方式

通常事件调用方式为 //版本1 public event NewEventHandler NewEvent;protected virtual void OnNewEvent(EventArgs e){ if (NewEvent != null) NewEvent(this, e);} 但这种方式的问题在于,在做NewEvent != null 检测之后,NewEvent事件调用之前,事件取消了注册,即NewEvent重新变成null,此时再进行调用,将会抛出异常 线程安全的做法, //版本2

《C++反编译与逆向分析技术揭秘》之学习笔记03--函数的调用方式

※函数的调用方式 EBP:扩展基址指针寄存器(extended base pointer) 其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部. ESP:(Extended stack pointer)是指针寄存器的一种,用于指向栈的栈顶. _cdecl:C/C++默认的调用方式,调用方平衡栈,不定参数的函数可以试用. 调用方:1.参数压栈.esp-=42.调用函数.3.实现栈平衡.esp+=4 此处的printf也是同样道理0x004010CB.0x004010CC两处压入参数,共8个字节

js实现类似于add(1)(2)(3)调用方式的方法

群里有人说实现类似add(1)(2)(3)调用方式的方法,结果马上有人回答: var add = function(a){ return function(b){ return function(c){ return a+b+c; }; }; }; add(1)(2)(3); //6 没错!那要是add(1)(2)(3)(4) 这样4个调用呢,那这个肯定不适用了. 这种就是类似于执行一个函数返回函数自身值: function add(x) { var sum = x; var tmp = fun

简述自己用过的几种异步调用方式

直接上代码 1.BeginInvoke和EndInvoke方式 private static void BeginInvoke1() { Func<int,string> fun = Todo; for (int i = 0; i < 5; i++) { //fun.BeginInvoke(i,TodoCallBack, fun); /* 异步调用委托BeginInvoke * handler.EndInvoke(x)为执行委托的结果 */ fun.BeginInvoke(i, x =&

同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式

1. 概念理解        在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式:   同步/异步主要针对C端: 同步:      所谓同步,就是在c端发出一个功能调用时,在没有得到结果之前,该调用就不返回.也就是必须一件一件事做,等前一件做完了才能做下一件事.   例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事 异步:      异步的概念和同步相对.当c端一个

简单解析三种JAVA调用方式-同步,异步,回调

模块之间有三种调用方式: 1.同步调用 同步调用很简单,就是直接调用方法B,必须等到方法A执行完才会继续执行原程序. 2.异步调用 异步调用,就是在A程序中给程序B一个实现,当B运行的时候,在满足条件的情况下能够调用A程序中的实现.举例说明 public interface Food { void eat(); } public class Cow implements Food { @override void eat() { syso("eat cow"); } } public

Saltstack的API接口与调用方式

saltstack看起来是成为一个大规模自己主动化运维和云计算管理的一个框架,类似于SDK,并非像puppet仅仅成为一个工具.基于良好设计的API和清楚的思路,让salt的二次开发变得非常easy.写非常少的代码就能够将salt跟现有的运维系统结合.saltstack是用python语言实现的,假设对saltstack本身进行二次开发,就必须得会python语言. Saltstack是通过多个独立的模块构成的,这些都能够当做saltstack的api.然后在上层做基础开发能够通过调用这些sal

Webservice 调用方式整理

前一段时间搞webservice,简单的记录了一下几种经常使用的调用方式,供大家參考. Java proxy 1).用过eclipse的创建web service client来完毕 2).在eclipse里面new一个web service client,会提示输入一个wsdl地址. 3).输入地址后直接点完毕,会自己主动生成代码.该方式和手动编写是一样的. 眼下该方式还没通:会咨询一下RD. Wsdl: http://10.81.21.92:8080/MailAdmin/ws/mailBox

java实现WebService 以及客户端不同的调用方式

java 实现WebService 以及不同的调用方式 webservice:    就是应用程序之间跨语言的调用    wwww.webxml.com.cn    1.xml    2.    wsdl: webservice description language web服务描述语言        通过xml格式说明调用的地址方法如何调用,可以看错webservice的说明书        3.soap simple object access protoacl (简单对象访问协议)