0
点赞
收藏
分享

微信扫一扫

贯通理解CC_CALLBACK_N(结合function、bind)

eelq 2022-04-26 阅读 176

笔者在入门cocos2d-x游戏开发时,希望实现一个功能:点击菜单项、转到下一个页面。这里用到的语句是:

bool HelloWorld::init()
{
    ......
    MenuItemFont* item1 = MenuItemFont::create("Start",
		CC_CALLBACK_1(HelloWorld::menuItem1Callback, this));
    ......
}

void HelloWorld::menuItem1Callback(Ref* pSender)
{
	MenuItem* item = (MenuItem*)pSender;
	log("Touch Start Menu Item %p", item);
}

create函数的功效是,点击菜单项,就调用参数里的“menuItem1Callback”函数。

很好奇,CC_CALLBACK_N是什么?它是如何实现回调函数的?为什么回调函数的形参是Ref*,为什么还传进去一个this指针?

一、回调函数的作用

回调函数本身的定义与普通函数无异,关键在于“回调”二字,就是说,它将被别的函数选择调用,别的函数不调用,它就不工作,反之就工作。这里,回调函数以函数指针的方式,传递到别的函数里作为参数,而不是被别的函数以代码块形式调用。

那么为什么需要回调函数以函数指针的形式传参进“主调函数”呢?因为我们希望赋予主调函数一种权限,就是它可以根据需要选择回调函数,这种选择就是通过回调函数指针的选取实现的。

举个例子,如下。

#include <iostream>

void programA_FunA1() { printf("I'am ProgramA_FunA1 and be called..\n"); }

void programA_FunA2() { printf("I'am ProgramA_FunA2 and be called..\n"); }

void programB_FunB1(void (*callback)()) {
  printf("I'am programB_FunB1 and be called..\n");
  callback();
}

int main(int argc, char **argv) {
  programA_FunA1();

  programB_FunB1(programA_FunA2);
}

二、std::function处理回调函数

std :: function是一个通用的多态函数包装器。 std :: function的实例可以存储,复制和调用任何可调用的目标 :包括函数,lambda表达式,绑定表达式或其他函数对象,以及指向成员函数和指向数据成员的指针

它的好处就在于,可以将回调函数妥为包装,作为参数传到别的函数里去,实现回调的过程。特别是和std::bind配合使用,更彰显其威力。

例如:function<int(int,int)> func;
则 function类的实例func可以指向 返回值为int型,有两个形参都为int型的函数。

注意,普通函数的函数名可以隐式转化为函数指针。

#include <functional>
#include <iostream>
int f(int a, int b)
{
  return a+b;
}
int main()
{
	std::function<int(int, int)>func = f;
	cout<<f(1, 2)<<endl;      // 3
	system("pause");
	return 0;
}

当function处理类的非静态成员函数时:

#include <iostream>
#include <functional>
using namespace std;

class Plus
{
public:
    int plus(int a, int b)
    {
        return a + b;
    }
};

int main()
{
  Plus p;
    function<int(Plus*,int, int)> f = &Plus::plus;
  //function<int(const Plus,int, int)> f = &Plus::plus;
    cout << f(p,1, 2) << endl;     //3
    system("pause");                                       
    return 0;
}

大家注意,Plus类里面的plus函数明明只有两个int参数,为什么在main函数里多了一个Plus&?为什么等号后面也多了一个&?

第一个问题的原因是,类的成员函数都会被隐式的传递一个this指针,因此Plus::plus的真实类型是:int(Plus*,int, int)。Plus*的作用是告诉编译器,这个非静态成员函数将使用哪个实例化对象来调用。如果想调用的是静态成员函数,就不存在这个问题,因为静态成员函数可以直接靠类调用。

第二个问题的原因是,编译器不会将对象的成员函数隐式转换成函数指针,因此必须在Plus::plus前加&。

三、bind绑定函数

std::bind的头文件是 <functional>,它是一个函数适配器,接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。

std::bind的返回值是可调用实体,可以直接赋给std::function

我们尝试将bind绑定类的非静态成员函数:

四、回到 CC_CALLBACK_N

 也就是说,CC_CALLBACK_N这一系列宏定义,都是bind的变体。只不过现在,我们不需要在成员函数前面加&,也不需要人工指定占位符了。

​
bool HelloWorld::init()
{
    ......
    MenuItemFont* item1 = MenuItemFont::create("Start",
		CC_CALLBACK_1(HelloWorld::menuItem1Callback, this));
    ......
}

void HelloWorld::menuItem1Callback(Ref* pSender)
{
	MenuItem* item = (MenuItem*)pSender;
	log("Touch Start Menu Item %p", item);
}

​

回到刚刚的代码。查源代码得:

MenuItemAtlasFont * MenuItemAtlasFont::create
(const std::string& value, const std::string& charMapFile, int itemWidth, int itemHeight, char startCharMap, 
const ccMenuCallback& callback)

typedef std::function<void(Ref*)> ccMenuCallback;

也就是说,create接受一个带有void(Ref*)类型的std::function。

这样一来,CC_CALLBACK_N中只有N=1适合。

于是我们传入回调函数名menuItem1Callback,又传入helloworld的指针this。

回调函数剩余的那个没被指定的参数是谁呢?查源代码可得,是该菜单项类型MenuItemAtlasFont的父类的父类MenuItem的一个指针,这个由系统通过其他方式调用,我们不用管。

最后一个问题,为什么typedef std::function<void(Ref*)> ccMenuCallback 要接受的是带有Ref*类型的回调函数呢?因为Ref是cocos的鼻祖类,传入它,是完全之策,不论你真正想使用哪一个类型,只需将Ref向下转换到实际的类型,或者使用多态调用即可。

完毕。同济大学 wst

举报

相关推荐

0 条评论