How to use (data_member | member function) pointers in class

1. 首先普通函数指针不能被赋值为成员函数的地址,即使返回类型和参数完全匹配

例如:下面是的pfi是一个普通函数指针,它没有参数,返回类型为int:
int (*pfi)();
若有两个全局函数,HeightIs()和WidthIs():
int HeightIs();
int WidthIs();

则下面的的赋值操作是合法的:
pfi = HeightIs;
pfi = WidthIs;

但类Screen也定义了两个访问函数 - height()和width(),它们也没有参数,返回类型也为int:
inline int Screen::height() { return _height; }
inline int Screan::width() { return _width; }
但是下面的赋值是非法的,会导致编译错误产生。
pfi = &Screen::height;

因为,成员函数有一个非成员函数不具有的属性 - 它的类(class)。

指向类成员函数的指针必须与其赋值的类成员函数类型匹配,不是两个方面而是三个方面:
(1)参数类型和个数(2)返回类型 (3) 它所属的类类型

类成员函数指针和普通函数指针之间的不匹配是由于这两种指针在表示上的区别。
普通函数指针存储函数的地址,可以被用来直接调用那个函数。
类成员函数指针首先必须被绑定在一个对象或者一个指针上,才能得到被调用对象的this指针,然后才调用指针所指的成员函数。


2. 成员函数指针的声明

拿下面这个类来说明:

class Screen {
public:
// 成员函数
void home() { _cursor = 0; }
void move( int, int );
char get() { return _screen[_cursor]; }
char get( int, int );
bool checkRange( int, int );
int height() { return _height; }
int width() { return _width; }
//....
private:
string _screen;
string::size_type _cursor;
short _height;
short _width;
};

成员函数指针的声明要求扩展的语法,它要考虑类的类型。
指向类数据成员的指针也是这样。
考虑Screen 类的成员_height 的类型,它的完整类型是“short 型的Screen 类的成员”,
指向_height 的指针的完整类型是“指向short 型的Screen 类的成员的指针”,这可以写为:
short Screen::*
指向short型的Screen类的成员的指针定义如下:
short Screen::* ps_Screen;
ps_Screen 可以用_height 的地址初始化如下
short Screen::*ps_Screen = &Screen::_height;

数据成员指针和普通指针之间的不匹配也是由于这两种指针的表示上的区别。普通指针含有引用一个对象所需的全部信息。数据成员指针在被用来访问数据成员之前,必须先被绑定到一个对象或指针上。

定义一个成员函数指针需要指定函数返回类型,参数表和类。
例如指向Screen 成员函数并且能够引用成员函数height()和width()的指针类型如下:
int (Screen::*) ( );
这种类型指定了一个指向类Screen的成员函数的指针,它没有参数,返回值类型为int。
指向成员函数的指针可被声明,初始化及赋值如下:
// 所有指向类成员的指针都可以用0赋值
int (Screen::*pmf1)( ) = 0;
int (Screen::*pmf2)( ) = &Screen::height;
pmf1 = pmf2;
pmf2 = &Screen::width;

也可以用typedef 定义,这样语法更容易读。如:
typedef int (Screen::*Action)( );
Action default = &Screen::home;
Action next = &Screen::forward;


3. 怎样使用指向类成员的指针

类成员的指针必须总是通过特定的对象或指向该类型的对象的指针来访问。是通过使用两个指向成员操作符的指针(针对类对象和引用的.* ,以及针对指向类对象的指针的->*)

(操作符.*和->*的说明如下:
pm-expression :
cast-expression
pm-expression .* cast-expression
pm-expression ->* cast-expression

The binary operator .* combines its first operand, which must be an object of class type,
with its second operand, which must be a pointer-to-member type.

The binary operator ->* combines its first operand, which must be a pointer to an object
of class type, with its second operand, which must be a pointer-to-member type.

In an expression containing the .* operator, the first operand must be of the class type
of the pointer to member specified in the second operand or of a type unambiguously derived
from that class.

In an expression containing the ->* operator, the first operand must be of the type "pointer
to the class type" of the type specified in the second operand, or it must be of a type
unambiguously derived from that class.)


如下面例子:

int (Screen::*pmfi)() = &Screen::height;
Screen& (Screen::*pmfS)( const Screen& ) = &Screen::copy;
Screen myScreen, *bufScreen;

// 直接调用成员函数
if ( myScreen.height() == bufScreen->height() )
bufScreen->copy( myScreen );

// 通过成员指针的等价调用
if ( (myScreen.*pmfi)() == (bufScreen->*pmfi)() )
(bufScreen->*pmfS)( myScreen );

类似地指向数据成员的指针可以按下列方式被访问:
typedef short Screen::*ps_Screen;
Screen myScreen, *tmpScreen = new Screen( 10, 10 );
ps_Screen pH = &Screen::_height;
ps_Screen pW = &Screen::_width;
tmpScreen->*pH = myScreen.*pH;
tmpScreen->*pW = myScreen.*pW;


4. 静态类成员的指针

在非静态类成员的指针和静态类成员的指引之间有一个区别:指向类成员的指针语法不能被用来引用类的静态成员,静态类成员是属于该类的全局对象和函数,它们的指针是普通指针

如:
class A{
public :
static void f( );
private:
static int m_data;
};

指向m_data指针的定义如下:
int *p = &A::m_data;
int A::* p = &A::m_data;
//错误

指向f 函数指针可以这样定义,它是一个普通的函数指针:
void (*ptrf)( ) = &A::f;

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

C++指针直接调用类成员函数

在编程工作中常会遇到在一个“类”中通过函数指针调用成员函数的要求,如,当在一个类中使用了C++标准库中的排序函数qsort时,因qsort参数需 要一个“比较函数”指针,如果这个“类”使用某个成员函数作“比较函数”,就需要将这个成员函数的指针传给qsort供其调用。本文所讨论的用指针调用 “类”的成员函数包括以下三种情况:

例1:将 “类”的成员函数指针赋予同类型非成员函数指针

#include

typedef void (*Function1)( ); //定义一个函数指针类型。
Function1 f1;

class Test1
{
 public:
  //…被调用的成员函数。
  void Memberfun1( ){ printf("%s /n","Calling Test3::Memberfun2 OK");};
  void Memberfun2()
  {
   f1=reinterpret_cast
(Memberfun1); //将成员函数指针赋予f1,编译出错
   f1();
  }
  //…
};

int main()
{
 Test1 t1;
 t1.Memberfun2();
 return 0;
}

例2:在一个“类”内有标准库函数,如qsort 或其他全局函数,用函数指针调用类的成员函数

#include

class Test2
{
private:
int data[2];
//…
public:
//…
int __cdecl Compare(const void* elem1, const void* elem2) //成员函数。
{
printf("%s /n","Calling Test2::Memberfun OK");
return *((int*)elem1)- *((int*)elem2) ;
}

void Memberfun()
{
data[0]=2; data[1]=5;
qsort( data, 2, sizeof(int), Compare);
//标准库函数调用成员函数。编译出错
}
//…
};

int main( )
{
Test2 t2;
t2.Memberfun(); //调用成员函数。
return 0;
}

例3:同一个“类”内,一个成员函数调用另一个成员函数

#include "stdlib.h"
class Test3
{
public:
//…
void Memberfun1( void (* f2)( ) ) { f2( ) ;} //成员函数1调用成员函数2
void Memberfun2( ) { printf("%s /n","Calling Test3::Memberfun2 OK");} //成员函数2
void Memberfun3( ) { Memberfun1( Memberfun2);}
// 编译出错
//…
};

int main( )
{
Test3 t3;
t3.Memberfun3(); //调用成员函数。
return 0;
}

以上三种情况的代码语法上没有显著的错误,在一些较早的编译环境中,如VC++ 4.0 通常可以编译通过或至多给出Warning。后来的编译工具,如VC++6.0和其他一些常用的C++编译软件不能通过以上代码的编 译, 并指出错误如下(以第三种情况用VC++ 6.0编译为例):
error C2664: 'Memberfun1' : cannot convert parameter 1 from 'void (void)' to 'void (__cdecl *)(void)'
None of the functions with this name in scope match the target type
即:Memberfun1参数中所调用的函数类型不对。

按照以上提示,仅通过改变函数的类型无法消除错误。但是如果单将这几个函数从类的定义中拿出来不作任何改变就可以消除错误通过编译。仍以第三种情况为例,以下代码可通过编译:

#include


void Memberfun1( void (* f2)( ) ) { f2( ) ;} //原成员函数1调用成员函数//2。
void Memberfun2( ) { printf("%s /n","Calling Test3::Memberfun2 OK");} //原成员函数2。
void Memberfun3( ) { Memberfun1( Memberfun2);}

int main( )
{
Memberfun3 ();
return 0;
}

由此可以得出结论,以上三种情况编译不能通过的原因并不在于函数类型调用不对,而是与 “类”有关。没通过编译的情况是用函数指针调用了 “类”的成员函数,通过编译的是用函数指针调用了非成员函数,而函数的类型完全相同。那么 “类”的成员函数指针和非成员函数指针有什么不同吗?

在下面的程序中,用sizeof()函数可以查看各种“类”的成员函数指针和非成员函数指针的长度(size)并输出到屏幕上。

#include "stdafx.h"
#include

#include


class Test; //一个未定义的类。

class Test2 //一个空类。
{
};

class Test3 //一个有定义的类。
{
 public:
  //...
  void (* memberfun)();
  void Memberfun1( void (* f2)( ) ) { f2( ) ;} //成员函数1调用成员函数2
  void Memberfun2( );//成员函数2。
  //…
};

class Test4: virtual Test3 ,Test2 //一个有virtual继承的类(derivative class)
{
 public:
  void Memberfun1( void (* f2)( ) ) { f2( ) ;}
};

class Test5: Test3,Test2 //一个继承类(derivative class)
{
 public:
  void Memberfun1( void (* f2)( ) ) { f2( ) ;}
};

int main()
{
 std::cout 《 "一般函数指针长度= "《 sizeof(void(*)()) 《 '/n';
 std::cout 《"-类的成员函数指针长度-"《'/n'《'/n';
 std::cout 《"Test3类成员函数指针长度="《 sizeof(void(Test3::*)())《'/n'《'/n';
 std::cout 《"Test5类成员函数指针长度="《sizeof(void (Test5:: *)())《'/n';
 std::cout 《"Test4类成员函数指针长度="《sizeof(void (Test4:: *)())《'/n';
 std::cout 《"Test类成员函数指针长度="《sizeof(void(Test::*)()) 《'/n';
 return 0;

}
输出结果为(VC++6.0编译,运行于Win98操作系统,其他操作系统可能有所不同):
一般非成员函数指针长度= 4
-类的成员函数指针长度-
Test3类成员函数指针长度=4
Test5类成员函数指针长度=8
Test4类成员函数指针长度=12
Test类成员函数指针长度=16

以上结果表明,在32位Win98操作系统中,一般函数指针的长度为4个字节(32位),而类的成员函数指针的长度随类的定义与否、类的继承种类和关系 而变,从无继承关系类(Test3)的4字节(32位)到有虚继承关系类(Virtual Inheritance)(Test4)的12字节(96位),仅有说明(declaration)没有定义的类(Test)因为与其有关的一些信息不明 确成员函数指针最长为16字节(128位)。显然, 与一般函数指针不同,指向“类”的成员函数的指针不仅包含成员函数地址的信息,而且包含与类的属性有关的信息,因此,一般函数指针和类的成员函数指针是根 本不同的两种类型。当然也就不能用一般函数指针直接调用类的成员函数,这就是为什么本文开始提到的三种情况编译出错的原因。尽管使用较早版本的编译软件 编译仍然可以通过,但这会给程序留下严重的隐患。

至于为什么同样是指向类的成员函数的指针,其长度竟然不同,从32位到128位,差 别很大,由于没有看到微软官方的资料只能推测VC++6.0在编译时对类的成员函数指针进行了优化,以尽量缩短指针长度,毕竟使用128位或96位指针在 32位操作系统上对程序性能会有影响。但是,无论如何优化,类的成员函数指针包含一定量的对象(Objects)信息是确定的。

那么如何用指针调用类的成员函数?可以考虑以下方法:

(1) 将需要调用的成员函数设为static 类型。如在前述例子2中,将class Test2 成员函数Compare 定义前加上static:

class Test2
{
//….
int static __cdecl Compare(const void* elem1, const void* elem2) //成员函数
//其他不变
}
改变后的代码编译顺利通过。原因是static 类型的成员函数与类是分开的,其函数指针也不包含对象信息,与一般函数指针一致。这种方法虽然简便,但有两个缺点:1、被调用的函数成员定义内不能出现任 何类的成员(包括变量和函数)2、由于使用了static 成员,类在被继承时受到了限制。

(2) 使用一个函数参数含有对象信息的static 类型的成员函数为中转间接地调用其他成员函数。以例3为例,将类Test3作如下修改,main( )函数不变,则可顺利通过编译:

class Test3
{
 public:
  //…
  void static __cdecl Helper(Test3* test3)
  {
   test3->Memberfun2();
  }
  void Memberfun1( void (* f2)(Test3*)) { f2(this) ;} //将对象信息传给Helper函数
  void Memberfun2( ) {printf("%s /n","Calling Test3::Memberfun2 OK"); } //成员函数2
  void Memberfun3( ) { Memberfun1( Helper);}
  //…
};
这种间接方式对成员函数没有任何限制,克服了第一种方法成员函数不能使用任何类的成员的缺点,但由于有static 成员,类的继承仍受到制约。

(3) 使用一个全程函数(global function)为中转间接调用类的成员函数。仍以例3为例,将代码作如下修改(VC++6.0编译通过):

class Test3;
void __cdecl Helper(Test3* test3);

class Test3
{
 public:
  //…
  void Memberfun1( void (* f2)(Test3*)) { f2(this) ;} //成员函数1调用成员函数2
  void Memberfun2( ) {printf("%s /n","Calling Test3::Memberfun2 OK"); } //成员函数2
  void Memberfun3( ) { Memberfun1(Helper);}
  //…
};

void __cdecl Helper(Test3* test3)
{
 test3->Memberfun2();
};
这个方法对成员函数没有任何要求,但是需要较多的代码。

除上述三种方法外还有其他方法,如可以在汇编层面上修改代码解决上述问题等,不属于本文范围。

结论:函数指针不能直接调用类的成员函数,需采取间接的方法。原因是成员函数指针与一般函数指针有根本的不同,成员函数指针除包含地址信息外,同时携带其所属对象信息

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值