• C++11 std::bind


    std::bind的基本概念

    • std::bind 是一个函数模板, 它就像一个函数适配器,可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个参数的函数ret,同时还可以实现参数顺序调整等操作。
    • 它的原型有两种形式,如下:
      // simple(1)    
      template <class Fn, class... Args>
        /* unspecified */ bind (Fn&& fn, Args&&... args);
       
       // with return type (2)    
      template <class Ret, class Fn, class... Args>
        /* unspecified */ bind (Fn&& fn, Args&&... args);
    • bind返回一个基于fn的函数对象(function object), 其参数被绑定到args上.
    • fn的参数要么是绑定到值,要么是绑定到placeholders(占位符,如_1, _2, …, _N)

      参数:

    • fn: 一个可调用对象(可以是function objects, 函数指针或引用,成员函数指针或成员变量指针), 它的参数将被args绑定args:
    • args:可变长参数,或者是具体的值,或者是占位符(placeholder). 注意:其长度必须与fn接收的参数个数一致
    • 返回值:

      令bind的返回值为ret:

      auto ret = std::bind(fn, args&&...)
      // or
      auto ret = std::bind<Ret>(fn, args&&...)

      其返回值ret是一个未指定类型T的function object。

    bind过程和std::placeholders的使用

      fn的参数要么是绑定到值,要么是绑定到placeholders

      1.完全绑定到值

    void f(int n1, int n2, int n3)
    {
        cout << n1 << " " << n2 << " " << n3 << endl;
    }
     
    // f的三个参数,全部绑定到值,对empty_args的调用将不需要提供参数
    auto empty_args = bind(f, 1, 2, 3);     
    empty_args();               // 1 2 3

      2.完全绑定到std::placeholders

    void f(int n1, int n2, int n3)
    {
        cout << n1 << " " << n2 << " " << n3 << endl;
    }
     
    int ret4() 
    { 
        cout << "ret4() called" << endl;
        return 4; 
    }
     
    // f参数都使用占位符绑定, 需要提供至少三个参数
    auto need_3args = bind(f, _1, _2, _3);
    need_3args(1, 2, 3);            // 1 2 3
    need_3args(1, 2, 3, 4, 5);      // 1 2 3; 4和5被丢弃
    need_3args(1, 2, 3, ret4());    // ret4() called<cr> 1 2 3; 会调用ret4(), 但是返回的4被丢弃

    bind过程分析及调用传参控制

      在分析bind的绑定过程时,如何确定bind调用有没有错误,以及调用bind返回值ret的时候如何正确传参?比如,对下面的bind调用:

    void f(int n1, int n2, int n3, int n4, int n5)
    {
        cout << n1 << " " << n2 << " " << n3 << " " << n4 << " " << n5 << endl;
    }
     
    // 如何分析bind的调用是否正确
    auto mix1 = bind(f, 1, 2, 3, _1, _2);
    auto mix2 = bind(f, 1, 2, 3, 4, _1, _2);
    auto mix3 = bind(f, 1, 2, 3, _2, _1);
    auto mix4 = bind(f, _3, 2, 3, 4, _1);
    auto mix5 = bind(f, _1, _1, _1, _1, _1);
    auto mix6 = bind(f, _100, _50, _10, _5, _1);
     
     
    // 如何填写mix的调用参数,他需要几个参数?
    // mix1(...); 
    // mix3(...); 
    // mix4(...); 
    // mix5(...); 

      

      1. bind(f, args…)的合法性分析

      设f需要的参数个数为N, bind(f…)中,提供的值的个数为V, 提供的占位符个数为S.对于合法的bind调用,必有 N == V + S. 如果V + S 超出N或者小于N, 编译都会报错。所以上面的mix定义中,只有mix1, mix3, mix4, mix5是合法的。mix2中参数个数已经有6个,而f只需要5个。mix6的参数个数对,但是占位符太大了, 在VC++(2013)编译器实现中最大为20。

      2. bind返回值ret的调用传参写法

      设bind(f, args…)中最大的占位符为_M. ( 如mix1, mix2, mix3中 M=2; mix4中 M=3; mix5中 M=1; mix6中M=100; )则:

      参数个数:

        ret的调用中至少要提供 M 个参数,因为_1~_M正是从ret(args…)参数列表中从左到右来按下标顺序绑定参数的,少于M个会报错,多于M个则被丢弃.

      参数顺序:

        ret(args…)中参数与placeholders: _1~_M的对应是很简单的,下标从1开始,依次对应。但是绑定到f的顺序是由bind(fn, args…)中placeholders的顺序决定的.如bind(f, _1, _2, _3, _4, _5)就是从左到右的顺序把ret(args…)里的参数绑定到f;而bind(f, _5, _4, _3, _2, _1)则是按相反的顺序绑定。

      于是,对于上文中定义的合法mix的调用示例及输出可以是:

    auto mix1 = bind(f, 1, 2, 3, _1, _2);
    auto mix3 = bind(f, 1, 2, 3, _2, _1);
    auto mix4 = bind(f, _3, 2, 3, 4, _1);
    auto mix5 = bind(f, _1, _1, _1, _1, _1);
     
    mix1(4, 5);                 // 1 2 3 4 5; M = 2;
    //mix1(4);                  // no, 参数太少了
    mix3(5, 4);                 // 1 2 3 4 5; M = 2; 
    mix4(5, 0, 1);              // 1 2 3 4 5; M = 3; 第一个参数5给_1, 第三个参数1给_3, 第二个参数0被丢弃,因为bind中没用到_2.
    mix5(5);                    // 5 5 5 5 5; M = 1;

      你可能注意到mix6中的_100, _50, 这样大的占位符是编译不过的。placeholders的最大值,在VC++上是20, 它的最大值依赖于具体的编译器实现,不需要纠结这个最大值. 一般不会使用很大的占位符,因为一旦你使用了一个占位符_100,虽然只用了一个,但是这就意味着调用者需要提供至少100个参数,你是想弄死谁?

    std::bind 绑定普通函数、lambda表达式

    include <functional>
     
    // 定义两个函数,乘、除法
    double multiply(double d1, double d2)
    {
        return d1 * d2;
    }
     
    double divide(double d1, int n)
    {
        assert(n != 0);
        return d1 / n;
    }
     
    //----------------------------- begin of new test -----------------------------
    RUN_GTEST(FunctorTest, Bind, @);        // google gtest 的简单封装,可以当做一个普通函数的开始!
     
    using std::bind;                        // for std::bind
    using namespace std::placeholders;      // for _1, _2, _3 ...
     
    // 做“无用功”, same函数跟multiply一样,接收两个参数,并且顺序也是一致
    auto same = bind(multiply, _1, _2);
    EXPECT_EQ(200.0, same(2, 100));         
     
    // 第一个参数使用占位符,第二个单数绑定为2.0, 从而返回一个函数仅需要传入一个参数,
    // 返回其2倍。
    auto doublize = bind(multiply, _1, 2.0);
    EXPECT_EQ(200.0, doublize(100));
     
    // 全部使用参数绑定,ret_20不需要参数即可调用,返回20
    auto ret_20 = bind(multiply, 2, 10);
    EXPECT_EQ(20.0, ret_20());
     
    // 正常的相除函数, arg1/arg2
    double d1 = divide(10, 2);
    EXPECT_EQ(5, d1);
     
    // 通过bind,交换两个参数的顺序,revertDivide(arg1, arg2)将返回arg2/arg1
    auto revertDivide = bind(divide, _2, _1);
    double d2 = revertDivide(10, 2);
    EXPECT_EQ(1 / 5.0, d2);
     
    // bind的第二种形式,显式指定返回值类型为int
    auto rounding = bind<int>(divide, _1, _2);
    auto i1 = rounding(10, 3);
    bool isSameType = is_same<int, decltype(i1)>::value;    // i1 是int
    EXPECT_TRUE(isSameType);
    EXPECT_EQ(3, i1);
     
    // bind lambda函数
    auto lambda_func = [](int x) -> int { return x; };
    auto ret_100 = bind(lambda_func, 100);
    EXPECT_EQ(100, ret_100);
     
    // 使用bind适配一个函数 
    std::function<void(int)> func_with_1args;
    func_with_1args = bind(multiply, 10, _1);
     
    END_TEST;

    std::bind 绑定类成员函数、成员变量

      成员函数区别于普通函数的一个特殊之处在于,其第一个参数必须是该类型的一个对象(或对象的指针或引用)

    class Foo
    {
    public:
     
        void f(int n1, int n2, int n3)
        {
            cout << n1 << " " << n2 << " " << n3 << endl;
        }
     
        int a_ { 100 };
    };
     
    //----------------------------- begin of new test -----------------------------
    RUN_GTEST(FunctorTest, Bind, @);
     
    Foo foo;
    Foo& foo_ref= foo;
     
    // 成员函数原型:Foo::f(int n1, int n2, int n3);
    // 使用Foo::f, 需要四个参数,1.Foo类型的对象(或其指针或引用); 2~4个参数则赋值给n1, n2, n3
     
    // 对返回值mfarg4的调用将需要4个参数: 按顺序绑定到占位符_1, _2, _3, _4.
    auto mfarg4 = bind(&Foo::f, _1, _2, _3, _4);
    // 使用对象本身调用
    mfarg4(foo, 10, 20, 30);        // 10 20 30; 
    // 使用对象指针调用
    mfarg4(&foo, 10, 20, 30);       // 10 20 30; 
    // 使用对象引用
    mfarg4(foo_ref, 10, 20, 30);    // 10 20 30; 
     
    // 对返回值mfarg3的调用将需要三个参数:分别绑定到三个占位符,f的第四个参数将使用固定左值30.
    auto mfarg3 = bind(&Foo::f, _1, _2, _3, 30);
    mfarg3(foo, 10, 20);            // 10 20 30; 
     
    // 对返回值mfarg2的调用将需要两个参数:分别绑定到两个占位符,f的后两个参数将使用固定左值20, 30.
    auto mfarg2 = bind(&Foo::f, _1, _2, 20, 30);
    mfarg2(foo, 10);                // 10 20 30; 
     
    // 对返回值mfarg1的调用将仅需要一个参数foo对象,绑定到唯一的占位符,后单个参数将使用bind中的固定左值10,20,30.
    auto mfarg1 = bind(&Foo::f, _1, 10, 20, 30);
    mfarg1(foo);                    // 10 20 30;
     
    // 完全使用左值绑定,返回值mfarg0将不需要使用参数进行调用.
    auto mfarg0 = bind(&Foo::f, foo, 10, 20, 30);
    mfarg0();                       // 10 20 30
     
    // 使用对象指针或引用也可以
    auto mfarg01 = bind(&Foo::f, &foo, 10, 20, 30);
    mfarg01();                      // 10 20 30
    auto mfarg02 = bind(&Foo::f, foo_ref, 10, 20, 30);
    mfarg02();                      // 10 20 30
     
    // 把一个成员函数赋值给一个std::function
    std::function<void(int, int, int)> normal_func;
    normal_func = bind(&Foo::f, foo, _1, _2, _3);
    normal_func(10, 20, 30);        // 10 20 30
     
    //------------------------------ 绑定成员变量a_ ---------------------------
     
    // bind成员变量,其第一个参数必须是该类型的一个对象(或对象或引用), 指针不行!!
     
    auto bind_mv = bind(&Foo::a_, _1);
    cout << bind_mv(foo);           // 100
    cout << bind_mv(foo_ref);       // 100
    //cout << bind_mv(&foo);        // error, 成员变量不能用对象指针来绑定
     
    END_TEST;

    std::bind 绑定模板函数

    / 定义一个函数模板,返回两数之和,返回值是两数之和的类型。使用了c++11中的trailing return types特性.
    template <typename T1, typename T2>
    auto add(const T1 & t1, const T2& t2) -> decltype(t1 + t2)
    {
        return t1 + t2;
    }
     
    // work with template function.
    auto addby2 = bind(add<double, double>, _1, 2.0);
    cout << addby2(10.2);                             // 12.2

    嵌套std::bind共享std::placeholder.

    void print(int n1, int n2, int n3)
    {
        cout << n1 << " " << n2 << " " << n3 << endl;
    }
     
    // 定义一个辅助函数
    auto addby1 = [] (int x) -> int 
    { 
        cout << "addby1() called" << endl;
        return (x+1); 
    };
     
    // 嵌套的bind在使用外层bind()中的placeholders
    auto nested_f = bind(print, _1, bind(addby1, _1), _2); 
     
    nested_f(1, 3);                 // addby1() called<cr> 1 2 3

    reference_wrapper<T>类型, 实现绑定引用

    nt x{10};
     
    // 第二个参数使用引用x,第三个参数使用值x
    auto bind_ref = bind(print, 1, std::cref(x), x);  
    bind_ref();                // 1 10 10; 
     
    x = 100;
     
    bind_ref();                // 1 100 10; 第二个参数跟着x变化了,第三个则没变

    bind与标准库协同工作

    RUN_GTEST(FunctorTest, BindPredefinedFunctors, @);
     
    // all predefined functors:
    // negate, plus, minus, multiplies, divides, modulus, equal_to, 
    // not_equal_to, less, greater, less_equal, greater_equal,
    // logical_not, logical_and, logical_or, bit_and, bit_or, bit_xor
     
    auto tenTimes = bind(multiplies<int>(), _1, 10);
    EXPECT_EQ(100, tenTimes(10));
    EXPECT_EQ(200, tenTimes(20));
    EXPECT_EQ(300, tenTimes(30));
     
    vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8 };
    // nested bind. output v[i] if 10*v[i] > 50.
    copy_if(v.begin(), v.end(),
        ostream_iterator<int>(cout, ", "),
        bind(greater<int>(),
            bind(multiplies<int>(), _1, 10),
            50));                               // 6,7,8,
    cr;
     
    END_TEST;

    bind与智能指针

    RUN_GTEST(FunctorTest, BindSmartPointer, @);
     
    struct Temp 
    {
        Temp(int i=0) : i_(i) {}
        void print() { pln(i_); }
        int i_;
    };
     
    vector<shared_ptr<Temp>> vs =
    {
        shared_ptr<Temp>(new Temp(1)),
        shared_ptr<Temp>(new Temp(2)),
        shared_ptr<Temp>(new Temp(3)),
    };
     
    for_each(vs.begin(), vs.end(), bind(&Temp::print, _1));  // 1<cr>2<cr>3<cr>
     
    bind(&Temp::print, vs[0])();        // 1
    bind(&Temp::print, vs[1])();        // 2
    bind(&Temp::print, vs[2])();        // 3
     
    END_TEST;

         bind中的参数是被copy或者是被move到目标函数的,除非显示指定按引用传递, 用std::ref 或者std::cref来包裹参数,否则是不会按引用来传递的。这意味着你要意识到, 在bind一个大的对象作为参数的时候可能存在的拷贝开销, 应该尽量用引用**

      同时,因为move会改变对象的状态,因此,所以当你在bind参数列表里重用placeholders的时候,要考虑到参数已经被moved掉的情况, 标准建议:只有在参数是左值或者不可移动的右值的时候,重用placeholders才有意义。

    本文来自博客园,作者:Mr-xxx,转载请注明原文链接:https://www.cnblogs.com/MrLiuZF/p/13336499.html

  • 相关阅读:
    C#之流程控制
    UML画图总结以及浅谈UNL九种图
    UML视频总结
    英语总结
    UML coming
    那天我把“小四”拆了
    first 关于文档(总结)
    机房收费需求分析文档
    梦开始的地方
    WebRTC 开发实践:为什么你需要 SFU 服务器
  • 原文地址:https://www.cnblogs.com/MrLiuZF/p/13336499.html
Copyright © 2020-2023  润新知