分类: C/C++
2015-03-16 14:50:36
昨天同事让帮忙写一小功能,才发现cocos2d-x 3.0 和 cocos2d-x 3.0rc0 差别还是相当大的。
发现Label这一个控件,3.0就比rc0版本多了一个创建函数,更为关键的是3.0内的Label锚点是在ccp(0.5,0.5),而一直3.0rc0是ccp(0,0)。
累觉不爱。尽管cocos2d-x改变太快,兼容性一次次的暴露出不足,但是,总归是向好的方向进行。于是下载了3.0来玩玩~
cocos new 出新的项目之后,仔细阅读代码,才发现了一句3.0区别于2.0的代码:
1 auto closeItem = MenuItemImage::create( 2 "CloseNormal.png", 3 "CloseSelected.png", 4 CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
2.0内的代码用的不是CC_CALLBACK_1而是menu_selector.
CC_CALLBACK系列是3.0基于c++11的特性新增的。CC_CALLBACK系列的定义如下:
1 // new callbacks based on C++11 2 #define CC_CALLBACK_0(__selector__,__target__, ...) std::bind(&__selector__,__target__, ##__VA_ARGS__) 3 #define CC_CALLBACK_1(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, ##__VA_ARGS__) 4 #define CC_CALLBACK_2(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, ##__VA_ARGS__) 5 #define CC_CALLBACK_3(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, ##__VA_ARGS__)
可以看出,CC_CALL_BACK系统后的数字,表示函数指针的参数个数。明白了这一点,选择CC_CALLBACK时,就不会出错鸟。
而看示例代码时,还会发现一个有意思的使用方法:
1 listener->onTouchesBegan = CC_CALLBACK_2(Layer::onTouchesBegan, this);
此时不禁要问onTouchesBegan又是啥,为啥不能直接函数指针赋值呢?
看定义就能明白了
1 std::function<void(const std::vector&, Event*)> onTouchesBegan;
因为CC_CALLBACK系列是std::bind,而onTouchesBegan是std::function来定义的。那么std::bind和std::function又有什么区别呢?
有博文说:
function模板类和bind模板函数,使用它们可以实现类似函数指针的功能,但却比函数指针更加灵活,特别是函数指向类的非静态成员函数时。
std::function可以绑定到全局函数/类静态成员函数(类静态成员函数与全局函数没有区别),如果要绑定到类的非静态成员函数,则需要使用std::bind。
标准库函数bind()和function()定义于头文件
std::bind绑定器
bind()接受一个函数(或者函数对象,或者任何你可以通过"(...)"符号调用的事物),生成一个其有某一个或多个函数参数被“绑定”或重新组织的函数对象。(译注:顾名思义,bind()函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。)例如:
1 int f(int, char, double); 2 auto ff = bind(f, _1, 'c', 1.2); // 绑定f()函数调用的第二个和第三个参数,返回一个新的函数对象为ff,它只带有一个int类型的参数 3 int x = ff(7); // f(7, 'c', 1.2);
参数的绑定通常称为"Currying"(译注:Currying---“烹制咖喱烧菜”,此处意指对函数或函数对象进行加工修饰操作), "_1"是一个占位符对象,用于表示当函数f通过函数ff进行调用时,函数ff的第一个参数在函数f的参数列表中的位置。第一个参数称为"_1", 第二个参数为"_2",依此类推。例如:
1 int f(int, char, double); 2 auto frev = bind(f, _3, _2, _1); // 翻转参数顺序 3 int x = frev(1.2, 'c', 7); // f(7, 'c', 1.2);
此处,auto关键字节约了我们去推断bind返回的结果类型的工作。
我们无法使用bind()绑定一个重载函数的参数,我们必须显式地指出需要绑定的重载函数的版本:
1 int g(int); 2 double g(double); 3 4 auto g1 = bind(g, _1); // 错误:调用哪一个g() ? 5 auto g2 = bind( (double(*)(double))g, _1); // 正确,但是相当丑陋
1 void H(int a); 2 //绑定全局函数 3 auto f11 = std::bind(H, std::placeholders::_1); 4 auto的类型实际上是std::function<void(int)> 5 6 //绑定带参数的成员函数 7 std::function<void (char*, int)> f = std::bind(&ReadHandler::ConnectPreProcess, this, std::placeholders::_1, std::placeholders::_1); 8 9 //三元函数转换成一元函数 10 int f(int, char, double); 11 // 绑定f()函数调用的第二个和第三个参数, 12 // 返回一个新的函数对象为ff,它只带有一个int类型的参数 13 auto ff = bind(f, _1, ‘c’, 1.2); 14 int x = ff(7);
自己写代码示例如下:
int Func(int x, int y); auto bf1 = std::bind(Func, 10, std::placeholders::_1); bf1(20); ///< same as Func(10, 20) int HelloWorld::AddFunc( int a, int b ) { return a + b; } bool HelloWorld::init() { auto bf2 = std::bind(&HelloWorld::AddFunc,this , std::placeholders::_1, std::placeholders::_2 ); auto result1 = bf2(10, 20); ///< same as a.Func(10, 20) std::function< int(int)> bf3 = std::bind(&HelloWorld::AddFunc, this, std::placeholders::_1, 100); auto result2 = bf3(10); ///< same as a.Func(10, 100) }
上面的例子中,bf1是把一个两个参数普通函数的第一个参数绑定为10,生成了一个新的一个参数的可调用实体体; bf2是把一个类成员函数绑定了类对象,生成了一个像普通函数一样的新的可调用实体; bf3是把类成员函数绑定了类对象和第二个参数,生成了一个新的std::function对象。看懂了上面的例子,下面我们来说说使用bind需要注意的一些事项:
std::function
它是函数、函数对象、函数指针、和成员函数的包装器,可以容纳任何类型的函数对象,函数指针,引用函数,成员函数的指针。
以统一的方式处理函数、函数对象、函数指针、和成员函数。允许保存和延迟执行函数。
function是一个拥有任何可以以"(...)"符号进行调用的值的类型。特别地,bind的返回结果可以赋值给function类型。function十分易于使用。(译注:更直观地,可以把function看成是一种表示函数的数据类型,就像函数对象一样。只不过普通的数据类型表示的是数据,function表示的是函数这个抽象概念。)例如:
1 typedef std::function<float (int x, int y)> f ;// 构造一个函数对象,它能表示的是一个返回值为float,两个参数为int,int的函数 2 struct int_div { // 构造一个可以使用"()"进行调用的函数对象类型 3 float operator() (int x, int y) const { return ((float)x)/y; }; 4 }; 5 6 void HelloWorld::testing() 7 { 8 f f1= int_div(); // 赋值 9 auto result3 = f1( 10, 2); 10 }
成员函数可被看做是带有额外参数的自由函数:
1 struct int_div { // 构造一个可以使用"()"进行调用的函数对象类型 2 float operator() (int x, int y) const { return ((float)x)/y; }; 3 int int_div_fun( int x ){ return x; }; 4 }; 5 typedef std::function<int (int_div*, int)> f_2; 6 7 bool HelloWorld::init() 8 { 9 f_2 f2 = std::mem_fn(&int_div::int_div_fun); // 指向成员函数 10 11 int_div int_div_object; 12 int v = f2(&int_div_object, 5); // 在对象x上用参数5调用X::foo() 13 std::function<int (int)> ff = std::bind( f2, &int_div_object, std::placeholders::_1); // f的第一个参数是&x 14 v = ff(5); // 调用x.foo(5) 15 16 17 }
ps:被vs2012的bug给坑了。因为看网上的代码于是刚开始第9行是这么写的:f_2 f2 = &int_div::int_div_fun;
然后就报错误:Error 1 error C2664: 'std::_Func_class<_Ret,_V0_t,_V1_t>::_Set' : cannot convert parameter 1 from '_Myimpl *' to 'std::_Func_base<_Rx,_V0_t,_V1_t> *'
查了一下,vs2010没有这个编译错误,但是2012有。2012必须得加上std::mem_fn才能编译。
1 struct int_div { // 构造一个可以使用"()"进行调用的函数对象类型 2 float operator() (int x, int y) const { return ((float)x)/y; }; 3 int int_div_fun( int x ){ return x; }; 4 5 int_div( std::function<void()>& f ):m_callback(f){}; 6 void Notify() 7 { 8 m_callback(); 9 } 10 std::function<void()> m_callback; 11 };
void Foo(int x, std::function<void(int)>& f) { if(x%2==0) f(x); } void G(int x) { cout<endl; } void H(int x) { cout< 2<<endl; } void TestFoo() { auto f = std::bind(G, std::placeholders::_1); Foo(4, f); //在Foo函数外面更改f的行为 f = std::bind(H, std::placeholders::_1); Foo(4, f); }
c++11中推出function是为了泛化函数对象,函数指针,引用函数,成员函数的指针,让我们可以按更统一的方式写出更加泛化的代码;推出bind是为了替换和增强之前标准库的bind1st和bind2st,让我们的用起来更方便!