`
javahigh1
  • 浏览: 1287665 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

成员函数

 
阅读更多

成员函数

假设有一个Point3d的指针和对象:

Point3d obj;

Point3d *ptr = &obj;

当进行如下操作:

obj.mormalize();

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++支持三种类型的成员函数:static、nonstatic、virtual,每一种被调用的方式都不相同。

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

C++的设计准则就是:非静态成员函数至少必须和一般的非成员函数有相同的效率。也就是说,如果我们要在以下两个函数之间做选择:

float magnitude3d( const Point3d *_this ){…}

float Point3d::magnitude3d() const{…}

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

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

float magnitude3d( const Point3d *_this ){

return sqrt( _this->_x*_this->_x +

_this->_y*_this->_y +

_this->_z*_this->_z );

}

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

1、改写函数的signature以安插一个额外的参数到成员函数中,用以提供一个存取管道,使class object得以调用该函数。该额外参数被称为this指针:

Point3d Point3d::magnitude( Point3d *const this )

如果member function是const,则变成:

Point3d Point3d::magnitude( const Point3d *const this )

2、将每一个“对非静态数据成员的存取操作”改为经由this指针来存取:

{

return sqrt( this->_x*this->_x +

this->_y*this->_y +

this->_z*this->_z );

}

3、将成员函数重新写成一个外部函数。对函数名称进行“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)的优化也已施行:

void magnitude__7Point3dFv( register const Point3d *const this, Point3d &__result )

{

Register float mag = this->magnitude();

__result.Point3d::Point3d();

__result.x = this->_x/mag;

__result.y = this->_y/mag;

__result.z = this->_z/mag;

}

静态成员函数(Static Member Functions)

静态成员函数由于缺乏this指针,因此差不多等同于非成员函数。如果Point3d::normalize()是一个静态成员函数,以下两个调用操作:

obj.normalize();

ptr->normalize();

将被转化为一般的nonmember函数调用,像这样:

//obj.normalize();

normalize__7Point3dSfv();

//ptr->normalize();

normalize__7Point3dSfv();

静态成员函数的主要特性就是它没有this指针,其次要的特性统统根源于这个主要特性:

n 它不能够直接存取其class中的nonstatic members

n 它不能够被声明为const、volatile或virtual

n 它不需要经由class object才被调用--虽然大部分时候它是这样被调用的

一个静态成员函数,会被提到class声明之外,并给予一个经过“mangling”的适当名称。例如:

unsigned int Point3d::object_count()

{

Return _object_count;

}

会被cfront转化为:

//在cfront之下的内部转化结果

unsigned int object_count_5Point3dSFv()

{

Return _object_count_5Point3d;

}

其中SFv表示它是个static member function,拥有一个空白(void)的参数链表。

如果取一个静态数据成员的地址,得到的将是其在内存中的位置,也就是其地址。由于静态成员函数没有this指针,所以其地址的类型并不是一个“指向类成员函数的指针”,而是一个“非成员函数指针”。也就是说:

&Point3d::object_count();

会得到一个数值,类型是:

unsigned int(*)();

而不是:

unsigned int( Point3d::* )();

虚拟成员函数(Virtual Member Functions)

虚函数的一般实现模型是:每一个类有一个虚表,内含该类之中各虚函数的地址,然后每一个对象有一个vptr,指向虚表的所在。在这一小节,将根据单一继承、多重继承和虚拟继承等各种情况,从细节上探讨该实现方式。

在单一继承的情况下,一个class只会有一个virtual table,每一个table内含其对应的class object中所有active virtual function函数实体的地址。这些active virtual function包括:

n 这个类所定义的函数实体,它会改写一个可能存在的base class virtual function函数实体。

n 继承自base class的函数实体,这是在derived class决定不该写virtual function时才会出现的情况

n 一个pure_virtual_called()函数实体,它既可以扮演pure virtual function的空间保卫角色,也可以当做执行期异常处理函数。


每一个虚函数都被指派一个固定的索引值,这个索引在整个继承体系中保持与特定的virtual function的关联。例如,在我们的Point class体系中:

class Point{

public:

virtual ~Point();

virtual Point& mult(float) = 0;

//...

float x() const { return _x; }

virtual float y() const { return 0; }

virtual float z() const { return 0; }

//...

protected:

Point( float x = 0.0 );

float _x;

}

virtual destructor被赋值slot1,而mult()被赋值slot2。此例并没有mult()的函数定义,所以pure_virtual_called()的函数地址会被放在slot2中。如果该函数意外地被调用,通常的操作是结束掉这个程序。y()被赋值slot3而z()被赋值slot4。X()的slot是多少?答案是没有,因为它并不是虚函数。在上图中,可以清楚地看到相关的内存布局及其virtual table。

当一个类派生于Point时,会发生什么事情?例如,类Point2d:

class Point2d:public Point{

public:

Point2d( float x=0.0, float y=0.0 ):Point(x),_y(y){}

~Point2d();

Point2d& mult(float);

//...

float x() const { return _x; }

float y() const { return 0; }

//...

protected:

float _x;

}

一共有三种可能性:

n 它可以继承base class所声明的virtual functions的函数实体。正确地说,是该函数实体的地址会被拷贝到派生类的virtual table相对应的slot之中。

n 它可以使用自己的函数实体。这表示它自己的函数实体地址必须放在对应的slot之中。

n 它可以加入一个新的virtual function。这时候virtual table的尺寸会增大一个slot,而新的函数实体地址会被放进该slot之中。

Point2d的virtual table在slot1中指出destructor,在slot2中指出mult()(取代pure virtual function)。它自己的y()函数实体地址放进slot3,继承自Point的z()函数实体地址则放在slot4。

类似情况,Point3d派生自Point2d,如下:

class Point3d : public Point2d{

public:

Point3d( float x=0.0, float y=0.0, float z=0.0 ):Point2d(x,y),_z(z){}

~Point3d();

Point3d& mult(float);

//...

float z() const { return _z; }

//...

protected:

float _z;

}

其virtual table中的slot1放置Point3d的析构函数,slot2放置Point3d::mult()函数地址。Slot3放置继承自Point2d的y()函数地址,slot4放置自己的z()函数地址。

现在,如果有如下的语句:

ptr->z();

那么,如何有足够的知识在编译时期设定virtual function的调用呢?

n 一般而言,我们并不知道ptr所指对象的真正类型。然而,我们知道,经由ptr可以存取到该对象的virtual table。

n 虽然不知道哪一个z()函数实体会被调用,但我们知道每一个z()函数地址都被放在slot4。

这些信息使得编译器可以将该调用转化为:

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

在这个转化中,vptr表示编译器所安插的指针,指向virtual table;4表示z()被赋值的slot编号。唯一一个在执行期才能知道的东西是:slot4所指的到底是哪一个z()函数实体?

在一个单一继承体系中,vritual function机制的行为十分良好,不但有效率而且很容易塑造出模型来。但是在多重继承和虚拟继承之中,对virtual functions的支持就没有那么美好了。

多重继承下的Virtual Functions

在多重继承中支持virtual functions,其复杂度围绕在第二个以及后继的基类身上,以及“必须在执行期调整this指针”这一点上。以下面的class体系为例:

class Base1{

public:

Base1();

virtual ~Base1();

virtual void speakclearly();

virtual Base1 *clone() const;

protected:

float data_Base1;

};

class Base2{

public:

Base2();

virtual ~Base2();

virtual void mumble();

virtual Base2 *clone() const;

protected:

float data_Base2;

};

class Derived : public Base1,public Base2{

public:

Derived();

virtual ~Derived();

virtual Derived *clone() const;

protected:

float data_Derived;

};

该多重继承体系的虚表布局情况如下所示:


首先,把一个从堆中配置而得的Derived对象的地址,指定给一个Base2指针:

Base2 *pbase2 = new Derived;

新的Derived对象的地址必须调整,以指向其Base2 subobject。编译时期会产生以下的代码:

Derived *temp = new Derived;

Base2 *pbase2 = temp ? temp + sizeof(Base1) : 0;

当程序员要删除pbase2所指的对象时:

delete pbase2;

指针必须再次被调整,以便再一次指向Derived对象的起始处。

一般规则是,经由指向“第二或后继之base class”的指针(或引用)来调用derived class virtual function,那么该调用操作所需“必要的this指针调整”操作,必须在执行期完成。也就是说,offset的大小,以及把offset加到this指针上头的那一小段程序代码,必须由编译器在某个地方插入。

调整this指针的另外一个负担是,由于两种不同的可能:(1)经由derived class(或第一个base class)调用,(2)经由第二个(或其后继)base class调用,同一函数在虚表中可能需要多笔对应的slots。例如:

Base1 *pbase1 = new Derived;

Base2 *pbase2 = new Derived;

//…

delete pbase1;

delete pbase2;

虽然两个delete导致相同的Derived destructor,但它们需要两个不同的virtual table slots:

n pbase1不需要调整this指针(因为Base1已经指向Derived对象都起始处)。其virtual table slot需放置真正的destructor地址。

n pbase2需要调整this指针,其virtual table slot需要相关的thunk地址。

Thunk解释:所谓thunk是一小段assembly代码,用来(1)以适当的offset值调整this指针,(2)跳到virtual function去。例如,经由一个Base2指针调用Derived destructor,其相关的thunk可能看起来是下面这个样子:

//虚拟C++代码

pbase2_dtor_thunk:

this += sizeof( base1 );

Derived::~Derived( this );

Thunk技术允许virtual table slot继续内含一个简单的指针,因此多重继承下不需要任何空间上的额外负担。slots中的地址可以直接指向virtual function,也可以指向一个相关的thunk(如果需要调整this指针的话)。

在多重继承下,一个derived class内含n-1个额外的virtual tables,n表示其上一层base classes的数目。对于本例而言,会有两个virtual table被编译器产生出来:

n 一个主要实体,与Base1(最左端base class)共享;

n 一个次要实体,与Base2(第二个base class)有关。

针对每一个virtual table,Derived对象中有对应的vptr。vptrs将在constructor(s)中被设立初值(经由编译器所产生出来的码)。

虚继承下的Virtual Functions

《深入探索C++对象模型》P168~169

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/wuliming_sc/archive/2009/01/31/3855878.aspx

分享到:
评论

相关推荐

    c++类成员函数作为回调函数

    在C++编程中,将类成员函数作为回调函数是一种常见的设计模式,特别是在处理异步操作、事件驱动编程或与库接口交互时。回调函数本质上是一个可以被其他代码调用的函数,它允许我们传递控制权给第三方代码并在特定...

    普通成员函数、类成员函数和类静态成员函数实例(VS2010)

    本实例主要探讨了三种类型的成员函数:普通成员函数、类成员函数和类静态成员函数。以下是对这三种函数类型的详细解释: 1. 普通成员函数(Non-static Member Functions): 普通成员函数是类的一部分,它们可以...

    C++ 线程函数是类的成员函数

    将线程函数定义为类的成员函数是一种常见的做法,它有助于封装相关数据和行为,使得代码更加模块化和易于管理。下面我们将详细讨论这个主题。 一、线程与成员函数 1. **线程的概念**:线程是操作系统分配处理器...

    如何让类的成员函数作为回调函数

    ### 如何让类的成员函数作为回调函数 #### 一、理解回调函数的特点及应用场景 在编程领域,回调函数是一种非常常见的设计模式,它允许程序员将一个函数作为参数传递给另一个函数,在适当的时候由后者来调用这个...

    C++将类的成员函数作为回调函数

    ### C++将类的成员函数作为回调函数 #### 背景与问题 在C++编程中,回调函数是一种常见的设计模式,它允许程序在特定的事件或条件下调用一个预先注册的函数。然而,当涉及到类的成员函数时,事情变得稍微复杂了...

    gtest有对类成员函数的例子

    当我们需要测试包含类成员函数的代码时,gtest提供了一套强大的机制来实现这一目标。本文将详细讲解如何在gtest中对类成员函数进行单元测试。 首先,我们需要理解类成员函数的概念。类是C++中的一个核心特性,它...

    定义一个形状类(抽象类)以及一个普通成员函数(用来重设形状大小)、两个纯虚成员函数-周长计算函数和面积计算函数

    ① 定义一个形状类CShape(抽象类),并添加一个普通成员函数(用来重设形状大小,重载成员函数)SetData()、两个纯虚成员函数-周长计算函数Perimeter()和面积计算函数Area(); ② 由CShape派生出一个圆形类...

    C++非静态成员函数完全可以作为线程函数

    一般地,线程要读写类的私有成员变量,只有两种方法:将全局函数声明为类友元friend,或者使用静态的成员函数static。那非静态的成员函数呢?一般地方都说的是,不行,做不了。。。为什么不行?都是代码区的代码,...

    直接调用类成员函数地址

    在C++中,成员函数的指针是个比较特殊的东西。对普通的函数指针来说,可以视为一个地址,在需要的时候可以任意转换并直接调用。但对成员函数来说,常规类型转换是通不过编译的,调用的时候也必须采用特殊的语法。C++...

    C++Hook(钩子)编程,通过内联汇编,使类成员函数代替全局函数(静态函数)[收集].pdf

    C++ Hook(钩子)编程,通过内联汇编,使类成员函数代替全局函数(静态函数) 本文研究了C++ Hook(钩子)编程中,使类成员函数代替全局函数(静态函数)的技术。通过内联汇编,构造类对象独享的函数(委托),完成了类成员...

    一般函数指针和类的成员函数指针

    然而,当涉及到类的成员函数时,事情变得稍微复杂一些,因为类的成员函数通常包含一个隐含的参数——`this`指针,用于引用调用该成员函数的对象实例。这导致了类的成员函数指针与普通函数指针在声明和使用上存在显著...

    通过函数指针调用C++非静态成员函数

    ### 通过函数指针调用C++非静态成员函数 在C++编程中,通过函数指针调用非静态成员函数是一种高级技巧,主要用于实现回调、动态绑定等场景。本文将详细探讨如何在Visual C++ 6.0 (VC6.0) 和Borland C++ Builder 6.0...

    const修饰类的成员函数

    ### const修饰类的成员函数 #### 一、概念与作用 在C++中,`const`关键字用于声明常量或指定变量不可修改。当我们提到`const`修饰类的成员函数时,主要是指该成员函数不会修改它所属于的对象的状态。这种机制有助...

    C++类与对象:static静态数据成员静态成员函数.doc

    ### C++类与对象:static静态数据成员与静态成员函数详解 #### 静态成员函数:类的公共工具箱 在C++编程语言中,类的**静态成员函数**扮演着一种特殊的角色,它既不属于任何特定的对象实例,而是整个类的属性。...

    APIHook、InlineHook库,使用C++11编写,可将回调函数绑定到类成员函数

    本库利用C++11的新特性,提供了一种优雅的方式来实现这些功能,并允许将回调函数直接绑定到类成员函数。 APIHook技术主要涉及到动态改变API调用的过程。它通常通过在调用API函数之前插入自定义代码来实现,使得原本...

    c++ demo,运算符重载,成员函数的实现

    本教程通过一个DEMO深入探讨如何在C++中实现运算符重载以及如何利用成员函数来完成这一过程。 首先,理解运算符重载的基本原理。在C++中,当我们对自定义类型(如类或结构体)使用运算符时,编译器会寻找与该运算符...

    C++友元成员函数使用实例

    本篇将深入探讨“C++友元成员函数”的使用实例。 首先,让我们理解友元函数的基本概念。友元函数不是类的成员,但它被声明为某个类的友元,因此它可以访问该类的所有私有和受保护成员。通常,友元函数定义在类的...

    对象和作用域限制符::在调用成员函数时的区别

    在C++编程语言中,对象和作用域限制符`::`在调用成员函数时扮演着不同的角色。首先,理解类和对象的概念是至关重要的。类是定义对象特性和行为的蓝图,它并不实际占用内存空间。而对象是类的具体实例,当创建一个类...

    Cstring成员函数详解

    Cstring成员函数详解 CString 类是 Visual C++ 中最常用的类之一,它提供了丰富的成员函数来操作字符串。下面将详细介绍 CString 类的所有成员函数。 构造函数 CString 类有多个构造函数,用于初始化 CString ...

    在子窗口中调用父窗口的成员函数的实例

    在子窗口中调用父窗口的成员函数的方法 1、用FindWindow("类名(可以是派生类)","窗口标题")或GetParent(),二者均能返回父窗口句柄,并能直接使用其成员函数。但是此法只能调用CWnd类里的成员函数,而不能调用自己...

Global site tag (gtag.js) - Google Analytics