引言
对于那种只在一两个地方使用的简单操作,lambda表达式是最有用的。 如果我们需要在很多地方使用相同的操作,通常应该定义一个函数,而不是多次编写相同的lambda表达式。 类似的,如果一个操作需要很多语句才能完成,通常使用函数更好 如果lambda的捕获列表为空,通常可以用函数来代替它。 但是,对应捕获局部变量的lambda,用函数来替换它就不是那么容易了。
- 例如:我们用在find_if调用中的lambda比较一个string和一个给定大小。
vector<string> v = { "ww","www","wwww","wwwww" };
int sz =3;
auto ret=find_if(v.begin(), v.end(), [sz](string s) {return s.size() > sz; });
cout << *ret << endl;
- 我们可以很容易地编写一个完成同样工作的函数
bool check_size(const string& s, string::size_type sz)
{
return s.size() > sz;
}
- 但是,我们不能用这个函数作为find_if的一个参数。
- 如前文所示,find_if接受一个一元谓词,因此传递给find_if的可调用对象必须接受单一参数。
为了用check_size来代替lambda,如何解决一元谓词接受一个参数的问题呢?
标准库bind函数
- 我们可以解决向check_size传递一个长度参数的问题。
- 方法是使用一个新的名为bind的标准库函数,它定义在头文件functional中。
- 可以将bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表
调用bind的一般形式为:
代码语言:javascript复制auto newCallable=bind(callable,arg_list);
- 其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。
- 即,当我们调用newCallable时,newCallable会调用callable,并将arg_list中的参数传递给callable函数。
- arg_list中的参数可能包含形如_n的名字,其中n是一个整数。
- 这些参数是“占位符”,表示newCallable的参数,他们占据了传递给newCallable的参数的“位置”.
- 数值n表示生产的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。
绑定check_size的sz参数
- 作为一个简单的例子,我们将使用bind生产一个调用check_size的对象,如下所示,它用一个定值作为其大小的参数来调用check_size:
using std::placeholders::_1;
//check6是一个可调用对象,接收一个string类型的参数
//并用此string和值6来调用check_size
auto check6 = bind(check_size,_1, 6);
- 此bind调用只有一个占位符,表示check6只接受一个单一参数。
- 占位符出现在arg_list的第一个位置,表示check6的此参数对应check_size的第一个参数。
- 此参数是一个const string&.
- 因此,调用check6必须传递给它一个string类型的参数,check6会将此参数传递给check_size.
string s = "hello";
bool b1 = check6(s);//check6(s)会调用check_size(s,6)
- 使用bind,我们可以将原来基于lambda的find_if调用:
auto ret=find_if(v.begin(), v.end(), [sz](string s) {return s.size() > sz; });
- 替换成如下使用check_size的版本:
vector<string> v = { "ww","www","wwww","wwwww" };
int sz =3;
auto ret=find_if(v.begin(), v.end(), bind(check_size,_1,sz));
cout << *ret << endl;
- 此bind调用生成一个可调用对象,将check_size的第二个参数绑定到sz的值。
- 当find_if对v中的string调用这个对象时,这些对象会调用check_size,将给定的string和sz传递给它.
- 因此,find_if可以有效的对输入序列中每一个string调用check_size,实现string的大小与sz的比较
使用placeholders名字
- 名字_n都定义在一个名为placeholders的命名空间中,而这个命名空间本身定义在std命名空间中。
- 为了使用这些名字,两个命名空间都要写上。
- 例如,_1对应的using声明为:
using std::placeholders::_1;
- 此说明我们要使用的名字_1定义在命名空间placeholders中,而此命名空间又定义在命名命名空间std中。
- 对每个占位符名字,我们都必须提供一个单独的using声明。编写这样的声明很烦人,可以使用另外一种不同的形式的using语句,而不是分别声明每个占位符,如下所示:
using namespace namespace_name;
- 这种形式说明希望来自namespace_name的名字都可以在我们的程序中直接使用。
- 例如:
using namespace placeholders;
- 使得placeholders定义的所有名字都可用。
- 与bind函数一样,placeholders命名空间也定义在functional头文件中
bind的参数
- 我们可以用bind绑定给定可调用对象中的参数或重新安排其顺序。
- 例如,f是一个可调用对象,它有5个参数,则下面对bind的调用:
void f(int a, int b, int c, int d, int e){}
void test()
{
//g是一个有两个参数的可调用对象
int a = 0, b = 0, c = 0;
auto g = bind(f, a, b, _2, c, _1);
}
- 生成一个新的可调用对象,它有两个参数,分别用占位符_2和_1表示。
- 这个新的可调用对象将它自己的参数作为第三个和第五个参数传递给f.
- f的第一个,第二个参数和第四个参数分别被绑定到给定的值a,b,c上。
- 传递给g的参数按参数位置绑定到占位符。即,第一个参数绑定到_1,第二个参数绑定到_2。
- 因此,当我们调用g时,其第一个参数将被传递给f作为最后一个参数,第二个参数将被传递给f作为第三个参数。
- 实际上,这个bind调用会将
g(_1,_2)
映射为
代码语言:javascript复制f(a,b,_2,c,_1)
- 即,对g的调用会调用f,用g的参数代替占位符,再加上绑定的参数a,b和c.
- 例如,调用g(x,y)会调用
f(a,b,y,c,x)
用bind重排参数顺序
- 下面是用bind重拍参数顺序的一个具体例子:
#include <iostream>
#include<vector>
#include<string>
#include<functional>
#include<algorithm>
using namespace std;
using namespace placeholders;
bool isShorter(string & s1,string& s2)
{
return s1.size()<s2.size();
}
void test()
{
vector<string> v = {"www", "wwww","ww","wwwww" };
cout << "未排序前:" << endl;
for_each(v.begin(), v.end(), [](string& s) {cout << s << " "; });
cout << endl;
cout << "按单词长度由短到长排序" << endl;
sort(v.begin(), v.end(), isShorter);
for_each(v.begin(), v.end(), [](string& s) {cout << s << " "; });
cout << endl;
cout << "按单词长度由长到短排序" << endl;
sort(v.begin(), v.end(), bind(isShorter, _2, _1));
for_each(v.begin(), v.end(), [](string& s) {cout << s << " "; });
cout << endl;
}
int main()
{
test();
system("pause");
return 0;
}
- 在第一个调用中,当short需要比较两个元素A和B时,它会调用isShorter(A,B).
- 在第二个对sort的调用中,出传递给isShorter的参数被交换过来了。
- 因此,当sort比较两个元素时,就好像调用isShorter(B,A);
绑定引用参数
- 默认情况下,bind的那些不是占位符的参数被拷贝到bind的返回的可调用对象中。
- 但是,与lambda类似,有时对有些绑定的参数我们希望以引用方式传递,或是要绑定参数类型无法拷贝,
- 例如,为了替换一个引用方式捕获ostream的lambda:
vector<string> v = {"www", "wwww","ww","wwwww" };
ostream &os=cout;
char c =' ';
for_each(v.begin(), v.end(), [&os, c](string& s) {cout << s << c; });
- 可以很容易编写一个函数,完成相同的工作
ostream& print(ostream& os, const string& s, char c)
{
return os << s << c;
}
- 但是,不能直接使用bind来代替对os的捕获:
//错误:不能拷贝os
for_each(v.begin(), v.end(),bind(print,os,_1,c));
- 原因在于bind拷贝其参数,而我们不能拷贝一个ostream,如果我们希望传递给bind一个对象而又不拷贝它,就可以用标准库函数ref函数:
vector<string> v = {"www", "wwww","ww","wwwww" };
ostream &os=cout;
char c =' ';
for_each(v.begin(), v.end(),bind(print,ref(os),_1,c));
- 函数ref返回一个对象,包含给定的引用,此对象是可以拷贝的。
- 标准库中还有一个cref 函数,生成一个保存const引用的类
- 与bind一样,函数ref和cref定义在头文件functional中
注意:
标准库中定义了两个分别为bindlst和bind2nd的函数。 与bind类似,这两个函数接收一个函数做参数,生成一个可调用对象,该对象调用给定函数,并将绑定参数传递给它。 但是,这些函数分别只能绑定第一个或者第二个参数。 由于这些函数局限太强,在新标准库中已经被弃用。 新的c 程序应该使用bind