绑定函数是我认为C++新标准里第二有用的库了 绑定库的使用环境是:
- GCC-C++ 4.3 及以上
- Visual Studio 2008 SP1 及以上
- BOOST 1.25及以上(其中function是1.16及以上)
先来看一段代码
#include <iostream>
#include <cstdio>
#include <memory>
#include <functional>
class button
{
public:
std::function<void(int)> onClick;
};
class player
{
public:
void play(void* sender, int param) {
printf("Play: %d => %d\n", (int)sender, param);
};
void stop(void* sender, int param) {
printf("Play: %d => %d\n", (int)sender, param);
};
};
button playButton, stopButton;
player thePlayer;
void connect()
{
playButton.onClick = std::bind(&player::play, &thePlayer, &playButton, std::placeholders::_1);
stopButton.onClick = std::bind(&player::stop, &thePlayer, &stopButton, std::placeholders::_1);
}
int main () {
connect();
playButton.onClick(0);
return 0;
}
// 以上代码参考boost中bind库示例代码,在G++ 4.6.1中测试通过
木有错,这是C++,并且很方便地实现了委托 这就是传说中的绑定库和增强型的函数对象 接下来一个一个来
Bind
可用于绑定函数、成员函数、函数对象、成员变量 这是老标准中std::bind1st和std::bind2nd的增强版,这两个函数只能且必须绑定一个带有两个参数的,并且只能且必须传入一个自定义参数。但是bind函数支持最多10个自定义参数。
int f(int a, int b)
{
return a + b;
}
// 以下代码等价
std::bind1st(std::ptr_fun(f), 5)(x); // 等于执行了 f(5, x)
std::bind(f, 5, std::placeholders::_1)(x); // 等于执行了 f(5, x)
在这里,std::bind函数返回绑定对象,后面跟的(x)表示传参x并执行。 std::placeholders::_1是占位符,表示调用时的第一个参数,这段代码里表示x 如果代码是std::bind(f, std::placeholders::_2, std::placeholders::_1)(x, y) 则表示执行 f(y, x) 另外,在调用前传入的函数参数会被复制并保存在std::bind返回的对象里,比如之前的std::bind(f, 5, std::placeholders::_1)中的5就被存储在了返回的对象里。所以为了性能上考虑,建议传入的类型为引用或指针,避免结构复制
另外,除了普通函数外,std::bind也支持成员函数,但是和普通函数不同,成员函数绑定的第二个参数必须是函数实例。如
struct X
{
bool f(int a);
};
X x;
std::shared_ptr<X> p(new X);
int i = 5;
std::bind(&X::f, ref(x), std::placeholders::_1)(i); // 相当于执行了 x.f(i)
std::bind(&X::f, &x, std::placeholders::_1)(i); // 相当于执行了(&x)->f(i)
std::bind(&X::f, x, std::placeholders::_1)(i); // 复制x,并执行(复制的x).f(i)
std::bind(&X::f, p, std::placeholders::_1)(i); // 复制智能指针p,并执行(复制的p)->f(i)
由于第二个参数的函数实例的保存方式与参数一致,所以也建议传入类引用或类指针,或者智能指针。 另外,std::bind还可以用于绑定成员变量,和函数结构,绑定函数变量的方法类似成员函数,绑定函数结构的方法类似普通函数 再来一个std::bind稍微复杂一点的应用的例子,和算法库配合使用
void foo(int& i){
-- i;
}
std::vector<int> vec;
// Blablabla..............
std::for_each(vec.begin(), vec.end(), std::bind(std::ref(foo)));
最后是bind函数的总结:
函数使用形式:
- bind(f [,t][,…]) // 自动推断
- bind<返回值类型>(f [,t][,…]) // 非自动推断
- bind<返回值类型, 函数类型, 绑定器传入参数类型>(f ,[…]) // 非自动推断
- bind<返回值类型, 函数参数类型, 绑定器传入参数类型>(f [,…]) // 非自动推断
- bind<返回值类型, 类, 绑定器传入参数类型(即对应的类实例)>(T::*f ,t [,…]) // 非自动推断
- bind<返回值类型, 类, 函数参数类型, 绑定器传入参数类型(即对应的类实例+函数传入的参数), >(T::*f ,t [,…]) // 非自动推断
绑定组合
需要注意的问题:
- 绑定参数数量不匹配将会在绑定时编译错误(特别注意的是绑定类成员时遗漏类实例)
- 绑定参数类型不匹配将会在调用时编译错误
- 占位符不匹配将会在调用时编译错误
- 绑定对象必须是函数或成员函数指针
- 绑定对象默认为c++函数且不支持变长参数函数,如printf,某些编译器上extern “C”的函数(如: std::strcmp)也不支持(经过检测,G++和VC++都没问题)
- 支持”__stdcall”, “__cdecl”, “__fastcall” 和 “pascal” 前缀,但是绑定这些函数时要注意加一些定义(boost库是这样,tr1不知道)
- 对于函数重载的绑定,由于绑定时不能自动确定是哪一个函数,所以会绑定失败,可以使用类型转换或使用局部变量指定这些函数(VC++支持对重载函数的函数类型推断)
- 由于std::bind的函数参数类型推断和传入参数类型推断是分开的,所以如果函数的参数是引用类型,绑定参数的时候一定要用std::ref(详见 https://www.owent.net/2012/558.html ),否则会复制临时对象传入的,而不是传入引用类型。如:
struct X
{
int& get();
int const& get() const;
};
int main()
{
std::bind( static_cast< int const& (X::*) () const >( &X::get ), _1 );
return 0;
}
// 或
int main()
{
int const& (X::*get) () const = &X::get;
std::bind( get, _1 );
return 0;
}
- bind函数的返回结果不包含STL中一元或二元函数的概念,因为其缺少result_type 和 argument_type 或 first_argument_type 和 second_argument_type的定义(经检测VC++和G++的bind返回结果包含result_type定义)(转换成std::function后如果是一元或二元函数则支持以上定义)
- 标准要求至少有10个占位符,而G++支持30个占位符
Function
这东西是针对函数对象的多态包装器(又称多态函数对象包装器),函数对象又称仿函数。 std::function的作用就在于把函数或函数对象转换成function对象,并用于保存和后期调用。 其中和std::bind的配合使用的例子上面已经有了,就不重复。 std::function同样支持函数、成员函数、函数变量和函数结构。 std::function和std::bind配合使用时是把std::bind返回的结果作为函数对象使用的。
函数对象的例程:
// 函数结构
struct int_div {
float operator()(int x, int y) const { return x / y; };
};
std::function<float (int x, int y)> f = int_div();
但是成员变量和成员函数稍有不同,在申明时函数第一个类型必须是类的类型(或指针),传入参数是也同样。如:
// 类成员变量
struct X {
int m;
};
X x;
x.m = 10;
std::function<int (X*) > f = &X::m;
printf("Function: class member %d\n", f(&x));
继续总结吧:
使用形式
std::function<函数申明>,如:std::function<int(int x, float)> fn;
std::function和函数指针的优劣
- std::function 允许任意可转换的函数
- std::function 可以和其他参数或函数绑定对象库配合使用
- 当空函数结构调用的时候 std::function 的行为可以预见, 类型安全
- 函数指针更小
- 函数指针更快(std::function 在析构时可能会释放函数对象)
- 函数指针对C语言库的向后兼容性更好
- 函数指针的错误信息更容易理解
性能
- 对象大小: 包含两个函数指针的大小
- 复制性能: 取决于所关联的函数或函数对象,建议采用函数或函数对象的引用传给std::function来提高复制性能
- 执行性能: 对一个正常的内联编译器而言,将会通过函数指针执行函数调用。如果关联到函数对象,析构时会进释放该对象