zoukankan      html  css  js  c++  java
  • C++11新特性:参数绑定——std::bind

    概述

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

    函数原型

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

    1. template< class F, class... Args >
    2. /*unspecified*/ bind( F&& f, Args&&... args );
    3.  
    4. template< class 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<T>::value == true

    T包含成员:

    1.对象成员

     一个由std::forward<F>(f)构造而来的std::decay<F>::type类型的对象,一个对象的每一个参数类型都是由std::forward<Arg_i>(arg_i)构造而来的std::decay<Arg_i>::type。简单来说,std::decay<F>::type对象保存了调用std::bind时传递过来的f参数,而若干个std::decay<Arg_i>::type则保存了传递过来的args参数(一个std::decay<Arg_i>::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<F>::type类型的对象将被调用, 它将会按照如下的方式来为a1, a2, …, ai 绑定值。
     ·如果调用bind时指定的是reference_wrapper<T>类型的,比如在调用bind时使用了std::ref 或者 std::cref来包装args,那么调用g内部的这个对象时,对应参数会以T&类型传入std::decay<F>::type类型的对象.
     ·如果在创建g时,使用了嵌套的bind,即g = bind(fn, args…)的参数列表args中,存在某个arg:使得std::is_bind_expression<decltype(arg)>::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<T>::value != 0). 那么a1, a2, …, ai会以转发的形式forward<ai>(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 <iostream>
    2. #include <functional>
    3.  
    4. void fn(int n1, int n2, int n3) {
    5. std::cout << n1 << " " << n2 << " " << n3 << std::endl;
    6. }
    7.  
    8. int fn2() {
    9. std::cout << "fn2 has called. ";
    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的高级用法还需要更深入学习。

  • 相关阅读:
    JavaSE-集合的遍历
    JavaSE-Collection常用方法
    JavaSE-异常
    JavaSE-匿名类_匿名内部类的使用
    JavaSE-内部类
    JavaSE-接口应用举例
    java线程的使用(Runnable)
    list根据所存对象属性排序
    Unable to locate appropriate constructor on class异常
    redis在java项目中的使用
  • 原文地址:https://www.cnblogs.com/zhoug2020/p/13583307.html
Copyright © 2011-2022 走看看