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

笔者在入门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绑定类的非静态成员函数:

class Base
{
    void display_sum(int a1, int a2)
    {
        std::cout << a1 + a2 << '\n';
    }
 
    int m_data = 30;
};
int main() 
{
    Base base;
    auto newiFunc = std::bind(&Base::display_sum, &base, 100, std::placeholders::_1);
    f(20); // should out put 120. 
}

注意,bind的参数先后为:要调用的非静态类成员函数指针(必须加&)、调用者地址(&)、绑定指定的参数或者占位符。这样一来,我们下次调用该函数时,只需人为指定一个参数,另一个参数由程序员已经制定好了。

bind可以返回一个std::function<void(int)>

这样有什么好处呢?不是有缺省参数吗?为什么要多此一举?

试想,假如有一个std::function类型  : std::function<void(int)>,原本这个回调函数display_sum是有两个参数,不可能成为该function类型的,现在我们人为砍掉他一个参数,就可以用bind将其转变为该function类型,实现调用。

四、回到 CC_CALLBACK_N

来看其定义:
 

#define CC_CALLBACK_0(__selector__,__target__, ...) 
std::bind(&__selector__,__target__, ##__VA_ARGS__)
#define CC_CALLBACK_1(__selector__,__target__, ...) 
std::bind(&__selector__,__target__, std::placeholders::_1, ##__VA_ARGS__)
#define CC_CALLBACK_2(__selector__,__target__, ...) 
std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, ##__VA_ARGS__)
#define CC_CALLBACK_3(__selector__,__target__, ...) 
std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, ##__VA_ARGS__)

不要被吓到了。我们刚刚提到,bind的参数分别为:要调用的非静态类成员函数指针(必须加&)、调用者地址(&)、绑定指定的参数或者占位符。现在CC_CALLBACK_1简化了这一过程,只需要传入不带&的成员函数名字和调用者指针即可。

 也就是说,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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_55360766

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值