我擦C++ 反人类啊
多继承二义性什么的反人类啊啊啊!!回调函数什么的也反人类啊!!
作为一名C# 程序猿表示实现个什么接口之类的挺好的,到C++ 就不一样了啊。事件委托什么的也很好用啊,到C++ 又不一样了啊!!!
好的~ 那么今天就来吐槽一下回调函数吧~
本来想实现个类似于C# 里面事件的东西来做类之间的通信,然后发现了凶残的回调函数,在cocos2d 里面是用的menu_selector 之类的东西,然后传进去个函数指针引用。。。嗯。。看起来很吊的样子,于是照葫芦画瓢写了一个,里面包含了个函数指针类型的成员。嗯,大致类似是这样:
1 //=================stdafx.h 2 #pragma once 3 4 #include "targetver.h" 5 6 #include <stdio.h> 7 #include <tchar.h> 8 9 #include "OBJ.h" 10 11 typedef void (OBJ::*XXFUNC) (void); 12 #define XXFUNC_SELECTOR(_p) (XXFUNC)(&_p) 13 14 //=================B.h 15 #pragma once 16 #include "stdafx.h" 17 class B : 18 public OBJ 19 { 20 private: 21 XXFUNC func;22 23 public: 24 void AddListener(XXFUNC func); 25 void TriggerEvent(); 26 B(void); 27 ~B(void); 28 };
这里的B 就相当于被观察者,负责触发事件给其添加的listener 函数。看起来是不是很像委托(C#)的用法..嗯..
然后来个观察者A:
1 #pragma once 2 3 #include "OBJ.h" 4 #include "B.h" 5 6 class A : public OBJ 7 { 8 private: 9 int m_xxxx; 10 B* m_b; 11 void callback(); 12 13 public: 14 A(void); 15 ~A(void); 16 void Print(void); 17 void AnotherPrint(void); 18 };
看起来没什么问题,哦对了,OBJ 是个啥都没有的基类,貌似为了使那个函数指针类型能过作为类的成员,定义指针类型的时候还像这样加了个OBJ::
1 typedef void (OBJ::*XXFUNC) (void);
好了,然后是类的实现了:
1 #include "StdAfx.h" 2 #include "B.h" 3 4 B::B(void) 5 { 6 this->func = NULL; 7 } 8 9 10 B::~B(void) 11 { 12 } 13 14 void B::AddListener(XXFUNC func) 15 { 16 this->func = func;
17 } 18 19 20 void B::TriggerEvent() 21 { 22 if (this->func) 23 { 24 (this->*func)(); 25 } 26 }
嗯。。。看上去没什么问题,然后是A:
1 #include "StdAfx.h" 2 #include "A.h" 3 #include "B.h" 4 5 6 A::A(void) 7 { 8 this->m_xxxx = 0; 9 this->m_b = new B(); 10 this->m_b->AddListener(XXFUNC_SELECTOR(A::callback)); 11 } 12 13 14 A::~A(void) 15 { 16 } 17 18 void A::callback() 19 { 20 this->Print(); 21 this->m_xxxx = 200000; 22 this->Print(); 23 } 24 25 void A::Print() 26 { 27 printf("%d\r\n", this->m_xxxx); 28 } 29 30 void A::AnotherPrint() 31 { 32 this->m_b->TriggerEvent(); 33 }
嗯,看上去也没啥问题,回调的时候输出两次自己的m_xxxx 的值嘛。。为了省事直接就内部实例化B,然后注册回调函数,对外有个AnotherPrint 让B 触发下事件,好吧可能这样设计有问题,不过不要在意这些细节,只是让监听者去触发了事件。。。。懒得改了,就这样吧。。
然后是main:
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 A a; 4 a.Print(); 5 a.AnotherPrint(); 6 return 0; 7 }
嗯,和刚才理由一样,懒得管B,所以就这样了。。
好了,运行看看效果吧,先后调用了Print 和AnotherPrint,也就是说,A实例化之后先输出了一次m_xxxx,然后触发了B 的事件,由于A 监听了这个事件,所以A 执行了callback,又调用了Print,修改了m_xxxx之后再调用次Print,理想输出应该是:
0 0 200000
好的,编译运行:
0 17830428 200000 请按任意键继续. . .
嗯。。。。不错,出来三个数,等等,中间那个是个啥?!?!这么有节奏的一串数字怎么混进去的!!一定是刚刚运行的方式不对,再试一下:
0 19534364 200000 请按任意键继续. . .
(╯°Д°)╯︵ ┻━┻ 你tm 在逗我!!
好(wo)~吧(cao)~看来只能调试了呢,然后发现了重大问题。。回调函数里的this 不是this 了啊!!!地址跟main 里面实例化的那个A 的地址不一样啊,你到底指的哪里啊亲,居然还能给成员赋值成功再打印出来啊!!你什么时候出现的啊!!!
这不科学啊!! 坑爹呢这是(对于C# 程序猿来说,在C# 的事件回调里的this 就代表上层作用域的那个类的那个实例)!!!
然后问了下同学,同学说“成员函数是没有被分配空间的啊魂淡,你不能这样玩的”。。。 听不懂啊完全不知道在说什么啊但是又好像很厉害的样子啊怎么办。
然后上网查了一下,找到了这样:http://www.cnblogs.com/this-543273659/archive/2011/08/17/2143576.html,还有这样:http://baike.baidu.com/link?url=P1TXY3WyaOT98mVsvTRHaZSZWcCIJsMPxpxOiFyXEhAgI4uUt8EkZ2C_tYWasUqn 的东西,哦~ 原来非静态成员函数含有个默认参数this,原来如此~ (喂,真的懂了么,怎么感觉没懂的样子) ,但是这个默认的this 到底在回调函数被调用的时候变成啥了啊!! 居然还能给成员赋值成功再打印出来啊!!你什么时候出现的啊!!!
好(wo)~吧(cao)~你(wan'er)赢(wo)了(ne)~ 于是我给那个函数指针类型加了个参数... 像这样:
1 typedef void (OBJ::*XXFUNC) (OBJ*); 2 #define XXFUNC_SELECTOR(_p) (XXFUNC)(&_p)
再把B 里面加上这俩东西:
private: OBJ* handler; public: void AddListener(OBJ* handler, XXFUNC func);
嗯,没错,addlistener 多了个OBJ* 的参数。。。 居然叫handler!!哪里来的勇气!!!
然后A 的回调变成了这样:
1 void A::callback(A* a) 2 { 3 a->Print(); 4 a->m_xxxx = 200000; 5 a->Print(); 6 }
好的~编译运行吧:
0 0 200000 请按任意键继续. . .
哈哈哈哈哈哈哈哈哈哈,居然成功了。。。 太不好玩了。。。。谁能告诉我那个this 到底指哪去了?!!居然还能给成员赋值成功再打印出来啊!!你什么时候出现的啊!!!
=================Edited on 01/12/2014======================
擦泪,今天又看了下cocos2d 里面传这种回调的实现,原来cocos2d 里面类似于我这个的东西确实在addListener 这种函数里面除了回调当作参数还真有传进去个OBJ,就像我的那个从
void AddListener(XXFUNC func);
变成
void AddListener(OBJ* handler, XXFUNC func);
一样,最重要的是在被观察者的调用这个指针的时候出现的不一样,我是这样调用的:
void B::TriggerEvent() { if (this->func) { (this->*func)(); } }
这里的this 是B 的那个this,应该是B* const 类型,但是cocos2d 里面是类似这种方法调用的:
void B::TriggerEvent() { if (this->func && this->handler) { (this->handler->*func)(); } }
没(diao)有(bao)错(le)!这个handler 是addListener 的时候加进去的那个,这样的话!我们就不需要把函数指针里面再加上那个OBJ* 的参数了!! 就可以直接用this 了!!!this 就正常了!!!!!嗯,我验证下。。。
哈哈哈哈哈哈哈哈哈哈果然是对的!!!
那么接下来的问题就是如果不用 this->handler->*func(); 来调用而使用this->*func(); 的时候在callback 里面的this 到底是啥,于是又debug 了下:
A: 0x0045fa50
B: 0x00296458
'this' in A::callback: 0x00296458 // 我擦果然是B !!!!
'this->m_b' in A::callback: 0x0045fa50 // 我擦把B 当成A 之后里面的m_b (B* 类型)居然指向的是A !!!! 什么乱起八灶的!!你指的这么乱你家里人知道么!!! (╯°Д°)╯︵ ┻━┻
嘛。。最后据说写blog 用到的代码都要传到github 上才叫nb。。,那么为了早日nb(好像哪里不对):