C++ Primer学习笔记 - 参数绑定bind
问题的引出
使用find_if时,可以用lambda表达式作为其第三个可调用对象的实参。然而,lambda表达式只适合用于简单的场景,如表达式只用一到两次。当可调用单元要要大量复用时,最好还是能写成函数形式,于是,我们写出check_size函数,用于过滤出尺寸大于sz的字符串。
bool check_size(const string& s, string::size_type sz)
{
return s.size() >= sz;
}
int main()
{
using std::placeholders::_1;
vector<string> vec = { "hello", "y", "no", "1234567" };
string::size_type sz = 2;
auto it = find_if(vec.begin(), vec.end(), [sz](const string& s) -> bool {
return s.size() >= sz;
});
cout << *it << endl;
return 0;
}
问题在于,find_if的第三个参数(可调用对象)只会给形参传入1个实参,而我们定义的check_size需要2个参数。该怎么办?
可以使用bind库函数解决该问题。
标准库bind函数
bind库函数可以看做是定义了一个新函数,能在原来函数基础上,只需要更少的传入参数,绑定原来函数所需要的参数,修改参数(列表中)的顺序。
头文件
其一般调用形式:
auto newCallable = bind(callable, arg_list);
/**
newCallable 是一个可调用对象
arg_list 是参数分隔的参数列表,对应给定的callable参数
*/
当我们调用newCallable 时,newCallable 会调用callable,并传入arg_list作为callable参数。
arg_list可能包含形如_n(n=1,2,3,...)的名字,作为“占位符”,表示newCallable的参数,占据了传递给newCallable 的参数的“位置”。比如,_1表示newCallable的第一个参数位置,_2表示newCallable 的第2个参数位置。
注意:
1)bind中的_1, _2等没有顺序要求;
2)_1, _2等占位符,代表的是newCallable对应位置中的参数,而非callable的;
3)_1, _2等决定了newCallable的形参个数,实参由newCallable的调用者决定。bind内除了第一个参数callable,其他参数都作为callable的实参,callable形参由其定义决定;
如何绑定callable的参数?
如上面例子中,如何绑定check_size的sz参数?
#include <functional>
int sz = 6;
auto checksz = bind(check_size, _1, sz);
string s = "hello"
bool b1 = checksz(s); // 调用checksz(s)等价于调用check_size(s, 6)
auto it2 = find_if(vec.begin(), vec.end(), checksz); // find_if会传入1个参数给checksz的_1参数,因此需要注意_1和sz的顺序不能颠倒
cout << *it2 << endl;
使用placeholders名字
前面的程序无法通过编译,无法识别像"_1"这样的标识符,因为它位于名为placeholders的命名空间,需要在使用前用using声明。
如,"_1"的using声明
using std::placeholders::_1;
...
每个占位符单独声明,很繁琐,我们可以用using指示,一次引入所有占位符:
using namespace std::placeholders;
...
bind的参数
可以用bind为可调用对象绑定参数,重新安排顺序,
如,f是一个可调用对象,有5个参数,g是生成的新可调用对象,有2个参数:
auto g = bind(f, a, b, _2, c, _1); // 调用g(_1, _2)会调用f(a, b, _2, c, _1)
用bind重排参数顺序
利用新可调用对象到原来可调用对象的映射,可以重新排列参数顺序。
比如,上面例子中,
auto g = bind(f, a, b, _2, c, _1); // 调用g(_1, _2)会调用f(a, b, _2, c, _1)
auto g2 = bind(f, a, b, _1, c, _2); // 调用g(_1, _2)会调用f(a, b, _1, c, _2)
再比如,
// 按单词长度由短至长排序
sort(words.begin(), words.end(), isShorter);
// 按单词长度由长至短排序
sort(words.begin(), words.end(), bind(isShorter, _2, _1));
当sort对2个元素A和B比较时,会调用isShorter(A, B)。在bind重排参数顺序后,会相当于调用isShorter(B, A)。
ref()绑定引用参数
默认情况下,bind中普通参数(非占位符(_1,_2,...))是以值拷贝方式传递给bind返回的新可调用对象中(比如上面例中的a,b,c),而非引用方式传值。然而,有时我们希望以引用方式传递参数,比如参数对象禁止拷贝,或者拷贝代价较高,该如何进行?
类似于lambda表达式中的值拷贝、引用拷贝。一样可以用标准库ref函数传递给bind对象引用作为参数。
ostream& print(ostream &os, const string& s, char c)
{
return os << s << c;
}
// 错误:不能拷贝os
for_each(words.begin(), words.end(), bind(print, os, _1, ' ')); // 调用newprint(v)会调用print(os, v, ' '), 值传递os会导致拷贝
// OK:引用传递os
for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' ')); // 调用newprint(v)会调用print(ref(os), v, ' '), 值传递os会导致引用拷贝,而os引用是允许拷贝的
另外,对于const对象,可以用cref函数来传递引用。ref和cref都位于
小结
1)当需要的可调用对象和现有函数由于参数数量、顺序不兼容,而又不想重新定义一个函数时,可以考虑使用bind库函数为现有函数绑定参数、调整参数顺序;
2)原函数需要引用传递参数时,可使用ref、cref库函数为bind传递对象引用;