为什么需要参数绑定
引入lambda表达式的原因:
- 像算法传递谓词时,由于一般的函数无法传递参数以外的信息,函数对象又比较麻烦,所以引入了lambda表达式
但是lambda表达式也有缺点:
- 在类似功能多次使用的时候,每次定义lambda表达式也会比较麻烦
因此,C++11标准库又引入了新特性:参数绑定bind
怎么用
// 功能:
bool istarget(const string& s){
return s.size() < 2;
}
vector<string> v{"This","is", "a", "predicate", "."};
auto found = find_if(v.begin(), v.end(), istarget);
cout << *found << endl;
上面代码可以可以找到第一个长度小于2的string ,但是如果我们希望在istarget中选择string时使用变量而不是固定的2的时候,应该怎么做呢?
根据需求定义比较函数
在本例中,就是定义一个接受选择对象string对象和最小长度参数的istarget函数:
bool istarget(const string& s, int sz){
return s.size() < sz;
}
使用参数绑定新的可调用对象
C++11标准库提供了一个bind函数,按照C++11 Primer的说法,可以将bind
函数看着一个通用的函数适配器,它接受一个新的可调用对象来“适应”原对象的参数列表。调用bind的一半形式为:
auto newCallable = bind(callable, arg_list);
也就是说:
auto isTarget = bind(istarget, std::placeholders::_1, 2);
使用定义的可调用对象
auto found = find_if(v.begin(), v.end(), isTarget);
cout << *found << endl;
istarget函数定义一次之后,可以使用bind函数适应各种算法的要求,从而实现了实现一次定义,多次使用的目标
如此,可调用对象就算凑齐了:函数,函数对象,lambada表达式,参数绑定。
接下来我们看看标准库中怎么说。
bind
头文件:
// STL
#include <functional>
// boost
#include <boost/bind.hpp>
using namespace boost;
- std::bind的基本形式如下:
- boost::bind的基本形式如下:
template<class R, class F> bind(F, f);
template<class R, class F, class A1> bind(F f, A1 a1);
namespace{ //匿名命名空间
boost::arg<1> _1;
boost::arg<2> _2;
... // 其他6个占位符定义
}
函数模板bind生成f的转发调用包装器。调用此包装器等价于绑定到 args 的参数调用 f
例如:
- 如果有一个函数func,它的形式时:
func(a1, a2);
- 那么,它将等价于一个具有无参operator()的bind函数对象调用:
bind(func, a1, a2)();
- 这是bind最简单的形式。bind表达式存储了func和a1、a2的拷贝,产生了一个临时函数对象。因为func接受两个参数,而a1和a2都是实参,因此临时函数对象将具有一个无参的operator()。当operator()调用发生时函数对象把a1、a2的拷贝传递给func,完成真正的函数调用。
参数:
- f - 可调用对象,包括函数指针、函数引用、成员函数指针、函数对象和lambda表达式等
- args - 要绑定的参数列表
- 参数的数量必须和f的参数数量相等,这些参数将会被传递给f作为入参
- boost最多可以接受9个参数
- STL支持绑定任意数量的参数
- 可以被placeholders 的占位符 _1, _2, _3… 所替换,占位符可以取代bind中参数的位置,在发生函数调用时才接收真正的参数。
- boost中的占位符位于匿名空间内,它们分别被定义为_1、_2、_3一直到_9
- STL中的占位符位于std::placeholders命名空间内
- 因此,STL中的占位符必须std::placeholders::_1,而boost中的占位符可以直接_1
- 参数的数量必须和f的参数数量相等,这些参数将会被传递给f作为入参
返回值:
- 返回一个函数对象
- 这个函数对象内部保存了f的拷贝,具有operator(),返回值类型被自动推导为f的返回值类型
- 这个函数对象满足 std::is_bind_expression
<
T>::value == true - 在发生调用时,这个函数对象将会把之前存储的参数转发给f完成调用
参数arg与占位符
- 占位符怎么理解呢?就是先把位置占好,然后将来会给这个位置传参
- 占位符的名字表示它在调用式中的顺序,而在绑定表达式中没有顺序的要求,_1不一定必须第一个出现,也不一定只出现一次,比如:
bind(func, _2, _1)(a1, a2);
返回一个具有两个参数的函数对象:
- 第一个参数将放在函数func的第二个位置
- 第二个参数则放在第一个位置
调用时等价于:
func(a2 , a1);
- 我们必须在绑定表达式中提供函数要求的所有参数,无论是真实参数还是占位符均可以。但是所有的参数必须和f要求的参数一致
int f(int a, int b){ // 二元函数
return a + b;
}
int g(int a, int b, int c){ //三元函数
return a + b + c;
}
binf(f, _1, 9)(x); // f(x, 9); 即9这个参数是固定的,x这个参数是不固定的
bind(f, _1, _2)(x, y); // f(x, y);
bind(f, _2, _1)(x, y); // f(y, x);
bind(f, _1, _1)(x, y); // f(x, x); y参数被忽略
bind(g, _1, 9, _2)(x, y); // g(x, 8, y);
bind(g, _3, _2, _2)(x, y, z); // g(z, y, y);
为占位符更名
类似_1、_2、_3这样的名字,觉得不好怎么办?可以为它起别名
// ------起别名-----
auto & _a = _1;
decltype(_2) & _b = _2;
//---------使用----
ref引用传参
- bind采用拷贝的方式存储对象和参数,如果参数很大或者无法拷贝,怎么办呢?可以使用ref库
#include <random>
#include <iostream>
#include <memory>
#include <functional>
using namespace std;
void f(int n1, int n2, int n3, const int& n4, int n5)
{
std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}
int main() {
// 演示参数重排序和按引用传递
int n = 7;
auto f1 = std::bind(f, std::placeholders::_2, 42, std::placeholders::_1, std::cref(n), n); // f1 = f(_1, 42, _2, &n, 7)
n = 10;
f1(1, 2, 1001); // 1 为 _1 所绑定, 2 为 _2 所绑定,不使用 1001 ====> f(2, 42, 1, 10, 7) 的调用
return 0;
}
bind嵌套使用
bind可以嵌套使用,一个bind表达式生成的函数对象可以被另一个bind再绑定,从而实现f(g(x))这样的效果
#include <random>
#include <iostream>
#include <memory>
#include <functional>
using namespace std;
using namespace std::placeholders; // 对于 _1, _2, _3...
void f(int n1, int n2, int n3, const int& n4, int n5)
{
std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}
int g(int n1)
{
return n1;
}
int main() {
auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
f2(10, 11, 12); // 进行到 f(12, g(12), 12, 4, 5); 的调用
return 0;
}
lambda表达式 VS bind
lambda表达式可以就地声明匿名函数,可以替代bind,其捕获列表[…]相当于绑定的变量,函数参数列表(…)相当于bind的占位符
使用
绑定普通函数/函数指针
int f(int a, int b){ // 二元函数
return a + b;
}
int g(int a, int b, int c){ //三元函数
return a + b + c;
}
// ------------------------------------
// ------------绑定普通函数--------------
// ------------------------------------
std::cout << bind(f, 1, 2) << "<===>"<< f(1, 2) << "\n"; // 输出3
std::cout << bind(g, 1, 2, 3) << "<===>"<< g(1, 2, 3) <<"\n"; // 输出7
// ------------------------------------
// ------------函数指针--------------
// ------------------------------------
typedef decltype(&f) f_type; //函数指针定义
typedef decltype(&g) g_type; //函数指针定义
f_type pf = f;
g_type pg = g;
// -----------绑定函数指针---------------------------
int x, y, z;
std::cout << bind(pf, _1, 9)(x) << "\n"; // (*pf)(x, 9);
std::cout << bind(pg, _3, _2, _2)(x, y, z) << "\n"; // (*pg)(z, y, y);
绑定重载函数
直接使用函数名的绑定方式存在一定限制,比如重载函数时bind无法确定具体要绑定哪一个,就会出现编译错误
怎么办呢?可以用typedef定义函数指针类型,在使用函数指针变量来绑定
但是这个方法不适合模板函数,因为我们很难写出一个准确的目标函数指针类型。怎么办呢?只能用lambda来解决
绑定变参函数
如果bind无法自动推导出返回值类型,这时我们必须显示指出返回值类型,比如要绑定print()
绑定成员函数
理论
bind也可以绑定类的成员函数。
X x
bind(&X::func, x, _1, _2, ...);
- 类的成员函数不同于普通函数,因为成员函数指针不能直接调用operator(),它必须被绑定到一个对象或者指针,然后才能得到this指针进而调用成员函数。
- 因此bind第一个参数必须是类的实例、引用或者指针
bind同样支持绑定虚成员函数,用法与非虚函数相同,虚函数的行为将由实际调用发生时的实例来决定。
举例
- 第一个例子:
struct demo{
int f(int a, int b){
return a + b;
}
};
// ------------------------------------
// ------------绑定成员函数--------------
// ------------------------------------
demo a, &rf = a; //类的实例对象和引用
demo *p = &a; //指针
// 我们必须在成员函数前加上取地址操作符&,表示这是一个成员函数指针,否则无法通过编译。
cout << bind(&demo::f, a, _1, 20)(10) << endl;
cout << bind(&demo::f, rf, _2, _1)(10, 20) << endl;
cout << bind(&demo::f, p, _1, _2)(10, 20) << endl;
- 第二个例子:
- bind能够绑定成员函数,这是个非常有用的功能,它可以替代标准库中令人迷惑的mem_func和mem_func_ref绑定器,用来配合标准算法中操作容器的对象。
- 下面的代码使用bind搭配标准算法for_each用来调试容器中所有对象的print()函数:
struct point{
int x, y;
point(int a = 0, int b = 0) : x(a), y(b){}
void print(){
cout << "(" << x << " , " << y <<")\n";
}
};
vector<point> v(10);
for_each(v.begin, v.end(), bind(&point::print, _1));
绑定函数对象
- 如果函数对象有内部定义的result_type,那么bind可以自动推导出所有的返回值类型,用法和普通函数一样
- 如果函数对象没有内部定义的result_type,那么需要在绑定形式上做一点改动,,比如:
bind<result_type>(functor, ...)
举个例子
- 内部的
bind(greater<int>(), _1, 10); //====》 x > 10
bind(plus<int >(), _1, _2); // ==== 》x + y
- 绑定自定义函数对象
// 自定义函数对象
struct func{
int operator()(int a, int b){
return a + b;
}
};
int main()
{
auto f = bind(func(), _1, 10);
cout << f(11) << "\n";
cout << bind(func(), _1, 10)(11) <<"\n";
cout << bind<int>(func(), _1, 10)(11); //用模板参数指明返回类型
return 0;
}
绑定成员变量
bind可以绑定类的public成员变量,用法和绑定成员函数类似。
选择处pair对象的first和second成员
自定义类
struct Foo {
Foo() = default;
Foo(int d) : data_(d) {
}
int data_ = 10;
};
int main()
{
Foo foo;
auto f4 = bind(&Foo::data_, _1);
cout << f4(foo) << '\n';
vector<Foo> fooVec{11, 12, 13, 14, 15, 15};
vector<int> intVec(fooVec.size());
/*
* bind(&Foo::data_, _1):取出Foo对象的data_
* transform调用bind表达式操作容器fooVec,将它的成员变量逐个填入intVec中
* */
transform(fooVec.begin(), fooVec.end(), intVec.begin(), bind(&Foo::data_, _1));
for(auto x : intVec){
cout << x <<",";
}
return 0;
}
#include <random>
#include <iostream>
#include <memory>
#include <functional>
using namespace std;
int main() {
// 常见使用情况:以分布绑定 RNG
std::default_random_engine e;
std::uniform_int_distribution<> d(0, 10);
std::function<int()> rnd = std::bind(d, e); // e 的一个副本存储于 rnd
for(int n=0; n<10; ++n)
std::cout << rnd() << ' ';
std::cout << '\n';
return 0;
}
第1、2、3… 调用都是相同的