我擦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(好像哪里不对):

https://github.com/strawhatboy/CppTuTsau_1

posted @ 2014-01-11 15:01  草帽过客  阅读(795)  评论(0编辑  收藏  举报