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传递对象引用;

posted @ 2021-12-03 09:49  明明1109  阅读(383)  评论(0编辑  收藏  举报