C++11新特性:参数绑定——std::bind

2021-03-30 12:26

阅读:467

标签:形式   type   std   传参   函数调用   过程   object   text   reference   

概述

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

函数原型

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

  1. templateclass F, class... Args >
  2. /*unspecified*/ bind( F&& f, Args&&... args );
  3.  
  4. templateclass R, class F, class... Args >
  5. /*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如何使用:

  1. #include
  2. #include
  3.  
  4. void fn(int n1, int n2, int n3) {
  5. std::cout " " " " std::endl;
  6. }
  7.  
  8. int fn2() {
  9. std::cout "fn2 has called.\n";
  10. return -1;
  11. }
  12.  
  13. int main()
  14. {
  15. using namespace std::placeholders;
  16. auto bind_test1 = std::bind(fn, 1, 2, 3);
  17. auto bind_test2 = std::bind(fn, _1, _2, _3);
  18. auto bind_test3 = std::bind(fn, 0, _1, _2);
  19. auto bind_test4 = std::bind(fn, _2, 0, _1);
  20.  
  21. bind_test1();//输出1 2 3
  22. bind_test2(3, 8, 24);//输出3 8 24
  23. bind_test2(1, 2, 3, 4, 5);//输出1 2 3,4和5会被丢弃
  24. bind_test3(10, 24);//输出0 10 24
  25. bind_test3(10, fn2());//输出0 10 -1
  26. bind_test3(10, 24, fn2());//输出0 10 24,fn2会被调用,但其返回值会被丢弃
  27. bind_test4(10, 24);//输出24 0 10
  28. return 0;
  29. }


技术图片

bind过程分析及传参控制

过程合法性分析

 设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的高级用法还需要更深入学习。

C++11新特性:参数绑定——std::bind

标签:形式   type   std   传参   函数调用   过程   object   text   reference   

原文地址:https://www.cnblogs.com/zhoug2020/p/13583307.html


评论


亲,登录后才可以留言!