再谈C++类成员函数的指针问题

babysloth 2001-01-21 03:27:00
关于C++类成员函数的指针问题,CSDN已经有很多人问过了,举个例子。
class A{
public:
void a(){}
};

出问题的原因就是有人一相情愿的声明
void (*test)=&A::a //Error:Cannot convert 'void (A::*)()' to 'void (*)()'
这就出了问题,因为类A没有实例,当然成员函数void a()就没有一个确定的地址。解决的办法也比较简单:
1.把void a()声明为static。不过这是个最笨最没人愿意干的办法,class A的一
般成员都不能访问了,那把void a()放在class A里面干什么呢?一般情况下不可取。
2.把指针声明改成void (A::*)test,那也就轻松过关。不过调用的时候却得找一个实例才能嫁得出去。

似乎问题已经完了,却还有一个问题。如果给出了实例
A aa;
又当如何呢?
CSDN有不少回答是这样的:
test=&(aa.a)甚至于test=aa.a
可是这样编译能通过吗?至少我没做到,诸位有什么办法。

我们继续。有人骂过这样用的人,真是无聊。其实这种用法相当普遍。在Borland
的C++ Builder里,比如一个TButton,它的OnClick就是一个函数指针,往往是指
向TForm的子类的一个成员函数。这时候上面的方法2又出了问题。方法2是预先知
道了类名才声明的函数指针,而我们的Form都是继承至TForm,类名根本就不知道
,而Borland却做到了这一点。激动地想看看Borland的高超水平,仔细看过,大
失所望。Borland增加了一个关键词__closure,以此声明的函数指针可以自动绑
定一个实例,调用就轻而易举了。
void (__closure *test)=aa.a;
test();
可怜我们这些遵守ANSI C++的却没办法改变编译器。

要预先得知还没有编出来的类的名字,我们的手段当然是template。借助模板,
这个问题也可以得到解决。可惜模板只对函数和类而言,为一个指针再来打造一
个类,是不是太浪费了一点?而且一但确定了,我们却不能再随心所欲地更改这
个指针,我们只能让这个指针在这个类里兜圈子,还要传递实例的参数,麻烦!

那我们怎么才能做到像Borland一样的效果呢?CSDN里有人是这么说的
“在函数入口处通过访问堆栈得到函数入口地址”

我的水平还不到这一步,我做不到。我也不知道这样干对于BC和VC是否同样有效
,但我相信C++能有一个圆满的答复,希望诸位能加入讨论一下。
...全文
666 1 收藏 32
写回复
32 条回复
切换为时间正序
请发表友善的回复…
发表回复
babysloth 2001-02-07
看来我老要磨牙,而且老是跟STL小组的两位斑竹磨,真是不好意思。
上次是争“继承是否破坏类的封装性”,这次都不知道说什么了,多留点口水上小组去,反正那里人少。
我说“我的水平还不到这一步,我做不到”,怎么又被倒过来说成了“babysloth你既然都到了这一地步了”?看来做好人难,做坏人也难。
“要的不是基础语法复习!”这话是说重了。前面乱答的人多了,我怕又来个敷衍了事的,所以故意说了这么一句,多多包涵哦。
我觉得问题解决得差不多了,任何想法可以接着写,等一下我就给分了。
回复
babysloth 2001-02-07
快成条件反射了,写类老是用C开头的多半是用VC的,用T开头的多半是泡在Borland里的。
上面这么多C开头的类了,来几个T开头的吧。
下面有一段模拟VCL里的事件指针的,勉强可以看看,虽然很难看。
程序在Turbo C++3.0,gcc,Borland C++5.5下都编译通过。
class TObject{};
class TControl: public TObject{};
class TNotifyEvent{
public:
TObject* Parent;
void (TObject::*Fn)(TObject*);
void operator() (TObject* Sender){(Parent->*Fn)(Sender);}
void init(TObject *p,void(TObject::*f)(TObject*)){Parent=p;Fn=f;}
};
#define NOTIFY_EVENT(A,B,C) A.init(&B,(void(TObject::*)(TObject*))&C);
class TButton: public TControl{
public:
TNotifyEvent OnClick;
};
class TForm: public TControl{
public:
void Click(TObject*){}
};
void main()
{
TButton b;
TForm f;
NOTIFY_EVENT(b.OnClick,f,TForm::Click);
b.OnClick(&b);
}
回复
babysloth 2001-02-07
来了就有分吧,呵呵,加分了。
回复
bugn 2001-02-07
刚才上来仔细想了一下babysloth的问题,其实你要问的不过是reinterpret_cast;因为C++的强类型检查让我们不能随便把指针乱赋值了,当然这也是好的程序设计风格的要求。但是用C++的一个好处就是他给你最大的灵活性,你如果真的想要怎样还是能怎样的。如果要跟babysloth磨牙的话,我就又要说了,"你看,这不又是基础语法学习了"

我一开是之所以很冒火的就是现在的人好像总是抓不住解决问题的要领。你看看csdn上的很多贴子,动不动就是用hook(偷窥狂们),汇编,驱动,实际上大多数问题解决的方向根本不在于此。

> CSDN里有人是这么说的“在函数入口处通过访问堆栈得到函数入口地址”我的水平还不到这一步,我做不到。

babysloth你既然都到了这一地步了,完全可以自己往前走的。我想除了一些复杂的算法以外,对于多数人来讲,在程序(软件)设计中遇到的问题都能独立解决的。很多人都害怕的是汇编和驱动,其实这又有什么呢?不过是多要一点点知识而已,这里并不需要非常强的挑战性的知识的。所以Assembler,Linker,Loader,Compiler,Kernel mode programming,这些确实需要些附加的知识(Hardware&OS),但我想诸位C/C++编程工作的人多少都要涉及这些知识吧,否则就去用Java之类(Java是统一的,从最底层的VM到最外层的应用全都在里面了)的好了。

做软件的时候创造性有时是最重要的,但如果基础不牢靠的话,一些有时你以为是困难或很高技巧的东西对很别人来讲只是一些正常解决问题的思路而已。

babysloth要还想再侃的话就发到小组来吧,那人也太少了 :(
回复
bugn 2001-02-06
to fengye:
那几个mem_fun???是不够的,除非你扩展出men_fun2??, mem_fun3??? ...
回复
fengye 2001-02-06
那个closure类用函数对象实现更C++ :)
回复
bugn 2001-02-06
另外感谢babysloth的提醒,我原来一直用的都是VC6的非标准扩展。今天才认识到了 ~_~
回复
bugn 2001-02-06
不好意思,我确实只在VC6下试过我的程序。因为我主要的开发平台都在win32上。我刚试了一下BCC5.5.1和GCC2.95.2,注意以下代码中的reinterpret_cast的用法,这样总满足需求了吧:

cfn = reinterpret_cast<CMD_FN> (&CBar::Fn2); // !!! note here
CLOSURE_FIRE(cfn, (102));

cfn = reinterpret_cast<CMD_FN> (&CBar::Fn1); // !!! note here
CLOSURE_FIRE(cfn, (101));
回复
babysloth 2001-02-06
又是一个编译通不过的,恐怕又用了VC++的扩展C++性能了吧???
回复
fengye 2001-02-06
> 我们却不能再随心所欲地更改这个指针,我们只能让这个指针在这个类里兜圈子
C++的风格,就是要这样,符合type safe原则

上面的程序可以在vc6下通过
回复
babysloth 2001-02-06
“呵呵,如果真的理解了的话,提出的问题根本就不算是个问题”
如果您编译通不过,可以试试这么改动,或者想个更好的办法。

#include <iostream.h>

class CFoo {
public:
typedef void (CFoo::*CMD_FN)(int);
void Fire(void);

virtual void Fn1(int dummy) {cout << "CFoo::Fn1(" << dummy << ")\n";}
void Fn2(int) {cout << "CFoo::Fn2\n";}
};

class CFooBar : public CFoo
{
public:
void Fn1(int dummy) {cout << "CFooBar::Fn1(" << dummy << ")\n";}
void Fn2(int) {cout << "CFooBar::Fn2\n";}
};

void CFoo::Fire()
{
CMD_FN fn;
fn = &CFoo::Fn1;
(this->*fn)(555);
fn = &CFoo::Fn2;
(this->*fn)(111);
}

void GlobalFire()
{
CFoo::CMD_FN fn;
CFooBar foo;
fn = &CFoo::Fn1;
(foo.*fn)(222);
fn = &CFoo::Fn2;
(foo.*fn)(333);
}

template<class T, class Fn>
class closure {
public:
T* m_this;
Fn m_fn;
closure(T* t, Fn fn) : m_this(t), m_fn(fn) {}
closure& operator=(Fn fn) {m_fn = fn; return *this;}
};
#define CLOSURE_FIRE(cl, x) ((cl.m_this)->*(cl.m_fn))x;

void main(void)
{
CFoo f1;

f1.Fire();
GlobalFire();

CFooBar f2;

closure<CFoo, CFoo::CMD_FN> cfn(&f2, &CFoo::Fn1);

CLOSURE_FIRE(cfn, (888));

cfn = (CFoo::CMD_FN)&CFooBar::Fn2; // !!! note here
CLOSURE_FIRE(cfn, (999));
}
回复
babysloth 2001-02-06
想法我觉得很好,只是您编译过了吗???
您确信编译能通过吗???
我用了g++和Borland的编译器,可惜都不能通过。
我在最前面已经说过了您在程序中所犯的这种错误,您也许没看吧?
不知道是不是我用的编译器的问题,不过我希望每个发表的程序应该至少编译过,负点责任比较好。
回复
jglin 2001-02-06
我的实现如下:
typedef void (*ACALLBACK)(DWORD dwUser);
class A
{
public:
virtual void a(){}
static void b(DWORD dwUser);
};

void A::b(DWORD dwUser)
{
A* pA = (A*)dwUser;
pA->a();
}

main()
{
A x;
ACALLBACK pf = A::a;
DWORD dwUser = x;
(*pf)(x);
}

只要是从A派生出来的,都可以重载函数a
回复
fengye 2001-02-06
我在想是否能用template做出能对付任意个参数的通用函数对象,似乎没有希望。
不过就具体应用来说,定义需要用几个也可以应付了。
回复
bugn 2001-02-05
刚刚想起来再加一例,注意其中的virtual

class CBar {
public:
void Fn1(int dummy) {cout << "CBar::Fn1(" << dummy << ") this[" << this << "]\n";}
virtual void Fn2(int) {cout << "CBar::Fn2\n";}
};

在上例中的main里最后加上
cfn = (CFoo::CMD_FN)CBar::Fn2; // !!! note here
CLOSURE_FIRE(cfn, (102));

cfn = (CFoo::CMD_FN)CBar::Fn1; // !!! note here
CLOSURE_FIRE(cfn, (101));
回复
bugn 2001-02-05
呵呵,如果你真的理解了的话,我觉得你提出的问题根本就不算是个问题。我说话一向有点不客气,请不要见怪,大家总是为了互相帮助互相学习嘛 :)

以下是一个用模版和宏的解决办法,如果不满足需求,可以提出问题:

class CFoo {
public:
typedef void (CFoo::*CMD_FN)(int);
void Fire(void);

virtual void Fn1(int dummy) {cout << "CFoo::Fn1(" << dummy << ")\n";}
void Fn2(int) {cout << "CFoo::Fn2\n";}
};

class CFooBar : public CFoo
{
public:
void Fn1(int dummy) {cout << "CFooBar::Fn1(" << dummy << ")\n";}
void Fn2(int) {cout << "CFooBar::Fn2\n";}
};

void CFoo::Fire()
{
CMD_FN fn;
fn = Fn1;
(this->*fn)(555);
fn = Fn2;
(this->*fn)(111);
}

void GlobalFire()
{
CFoo::CMD_FN fn;
CFooBar foo;
fn = CFoo::Fn1;
(foo.*fn)(222);
fn = CFoo::Fn2;
(foo.*fn)(333);
}

template<class T, class Fn>
class closure {
public:
T* m_this;
Fn m_fn;
closure(T* t, Fn fn) : m_this(t), m_fn(fn) {}
closure& operator=(Fn fn) {m_fn = fn; return *this;}
};
#define CLOSURE_FIRE(cl, x) ((cl.m_this)->*(cl.m_fn))x;

void main(void)
{
CFoo f1;

f1.Fire();
GlobalFire();

CFooBar f2;

closure<CFoo, CFoo::CMD_FN> cfn(&f2, f1.Fn1);

CLOSURE_FIRE(cfn, (888));

cfn = (CFoo::CMD_FN)f2.Fn2; // !!! note here
CLOSURE_FIRE(cfn, (999));
}
回复
babysloth 2001-02-05
很感谢您的答复,不过很遗憾您说的话我在前面都说过了。我也不明白同一个问题为什么老是解决不了呢???
现在需要的解决这么一个问题,而不是基础语法的重复了。
希望是能把一个所谓的“指针”(可以是您自己定义的模板等等什么都可以)能指向成员函数,绑定一个实例。
调用的时候不用再指出这个实例了。并且这个“指针”还可以改变指向其他成员函数。
就像BCB里加了__closure一样。
需要的不是基础语法复习!
回复
bugn 2001-02-05
再补充一点:在给__closure类型的赋值的时候,就顺便已经把对象指针附带在上面了,所以看起来象是单独的C函数,但实际上不是的。
回复
bugn 2001-02-05
在补充一点,因为普通成员函数(非static的)都要有一个this指针隐含作为参数传递进去的,所以 !! 必须 !! 有一个实例化的对象才能调用。
回复
bugn 2001-02-05
早就回答过了,我不明白为什么同一个问题要问多少次呢:

http://www.csdn.net/expert/Topic/58518.shtm
回复
发动态
发帖子
C语言
创建于2007-09-28

6.2w+

社区成员

C语言相关问题讨论
申请成为版主
社区公告
暂无公告