菜鸟笔记
提升您的技术认知

c 11新特性:参数绑定——std::bind-ag真人游戏

 std::bind函数定义在头文件functional中,是一个函数模板,它就像一个函数适配器,接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收n个参数的函数fn,通过绑定一些参数,返回一个接收m个(m可以大于n,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

std::bind函数有两种函数原型,定义如下:

template< class f, class... args >
/*unspecified*/ bind( f&& f, args&&... args );
template< class r, class f, class... args >
/*unspecified*/ bind( f&& f, args&&... args );

std::bind返回一个基于f的函数对象,其参数被绑定到args上。


f的参数要么被绑定到值,要么被绑定到placeholders(占位符,如_1, _2, ..., _n)。

f:一个可调用对象(可以是函数对象、函数指针、函数引用、成员函数指针、数据成员指针),它的参数将被绑定到args上。

args:绑定参数列表,参数会被值或占位符替换,其长度必须与f接收的参数个数一致。

调用std::bind的一般形式为:

auto newcallable = std::bind(callable, arg_list);

 其中,newcallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newcallable时,newcallable会调用callable,并传递给它arg_list中的参数。

std::bind的返回类型是一个未指定类型t的函数对象,这个类型t满足以下条件: std::is_bind_expression::value == true

t包含成员:

1.对象成员

 一个由std::forward(f)构造而来的std::decay::type类型的对象,一个对象的每一个参数类型都是由std::forward(arg_i)构造而来的std::decay::type。简单来说,std::decay::type对象保存了调用std::bind时传递过来的f参数,而若干个std::decay::type则保存了传递过来的args参数(一个std::decay::type保存一个args)。

2.构造函数

 如果t的所有对象成员都是可拷贝的,则它自身也是可拷贝的;如果它的所有对象成员都是可移动构造的,则它自身也是可移动构造的。

3.成员类型result_type(从c 17开始result_type已经被弃用)

·如果f是函数指针或者成员函数指针,result_type就是f的返回值类型

·如果f是一个拥有(或者说定义了)result_type的类类型,那么t的result_type就是f::result_type,即使result_type已经在t中被定义过

4.成员函数operator()

 这是最应该了解的,因为在实际使用过程中,我们调用std::bind得到的返回值就是用来作为函数调用的。

 bind的返回值t,假设我们这样调用:g(a1, a2, a3, … ai); 此时g内部保存的std::decay::type类型的对象将被调用, 它将会按照如下的方式来为a1, a2, …, ai 绑定值。

 ·如果调用bind时指定的是reference_wrapper类型的,比如在调用bind时使用了std::ref 或者 std::cref来包装args,那么调用g内部的这个对象时,对应参数会以t&类型传入std::decay::type类型的对象.

 ·如果在创建g时,使用了嵌套的bind,即g = bind(fn, args…)的参数列表args中,存在某个arg:使得std::is_bind_expression::value == true, 那么这个嵌套的bind表达式会被立即调用,其返回值会被传给ret里的_myfun作为参数(也就是说嵌套的bind返回值会被当做ret调用时的参数), 如果嵌套的bind里用到了占位符placeholder, 这些placeholder将会从ret的调用参数ret(a1, a2, … ai)中对应位置选择.

 ·如果在创建g时,使用了占位符placeholders, 即 g = bind(fn, arg1, arg2, …, _1, _2, …), (对于_1, _2…, 有std::is_placeholder::value != 0). 那么a1, a2, …, ai会以转发的形式forward(ai)传递给_myfun, a1对应_1, a2对应_2, 以此类推.

 否则,ret内部保存的args,即上文提到的_mybargs(bind调用时绑定的参数们)将被以左值的形式传给_myfun以完成调用,这些参数和g有相同cv限定属性.

 如果g(a1, a2, …, ai)中,有哪些ai没有匹配任何的placeholders,比如在调用bind时,placeholder只有_1, 而g(a1, a2, a3), 那么a2, a3就是没有匹配的,没有被匹配的参数将被求值,但是会被丢弃。

 如果g被指定为volatile(volatile or const volatile),结果是未定义的。

上述内容都可以在c 文档中找到。

从实践出发,看下面一段程序来理解std::bind如何使用:

#include 
#include 
void fn(int n1, int n2, int n3) {
	std::cout << n1 << " " << n2 << " " << n3 << std::endl;
}
int fn2() {
	std::cout << "fn2 has called.\n";
	return -1;
}
int main()
{
	using namespace std::placeholders;
	auto bind_test1 = std::bind(fn, 1, 2, 3);
	auto bind_test2 = std::bind(fn, _1, _2, _3);
	auto bind_test3 = std::bind(fn, 0, _1, _2);
	auto bind_test4 = std::bind(fn, _2, 0, _1);
	bind_test1();//输出1 2 3
	bind_test2(3, 8, 24);//输出3 8 24
	bind_test2(1, 2, 3, 4, 5);//输出1 2 3,4和5会被丢弃
	bind_test3(10, 24);//输出0 10 24
	bind_test3(10, fn2());//输出0 10 -1
	bind_test3(10, 24, fn2());//输出0 10 24,fn2会被调用,但其返回值会被丢弃
	bind_test4(10, 24);//输出24 0 10
	return 0;
}



过程合法性分析

 设f需要的参数个数为n, bind(f…)中,提供的值的个数为v, 提供的占位符个数为s。对于合法的bind调用,必有 n == v s. 如果v s 超出n或者小于n, 编译都会报错。

bind返回值的传参调用

·参数个数

f的调用中提供的参数与占位符数量有关,从程序中可以看出。

·参数顺序

参见程序运行结果,参数顺序与std::placeholders中的顺序一致,因此我们可以用bind来重排参数顺序。

这些只是std::bind的基本用法,对std::bind的引入是c 11的一大亮点,将其与lambda表达式、智能指针、绑定引用参数等知识相结合会明显改变原有的代码编写。std::bind的高级用法还需要更深入学习。

网站地图