C++0x FAQ中文版:std::function 和 std::bind

来源:互联网 发布:wp怎么下载软件 编辑:程序博客网 时间:2024/04/25 22:02

C++0x FAQ中文版:std::function 和 std::bind - [C++11 FAQ]

加入微群学C++,更有趣,更轻松:)

此博客已经停止更新,并乔迁新址http://chenlq.net,感谢大家对本博客的喜爱,希望在新的站点能够继续和大家一起交流学习。谢谢大家:)

std::function 和 std::bind

标准库函数bind()和function()定义于头文件<functional>中(该头文件还包括许多其他函数对象),用于处理函数及函数参数。bind()接受一个函数(或者函数对象,或者任何你可以通过"(...)"符号调用的事物),生成一个其有某一个或多个函数参数被“绑定”或重新组织的函数对象。(译注:顾名思义,bind()函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。)例如:
    
        int f(int, char, double);
        auto ff = bind(f, _1, 'c', 1.2);    // 绑定f()函数调用的第二个和第三个参数,返回一个新的函数对象为ff,它只带有一个int类型的参数
        int x = ff(7);                //  f(7, 'c', 1.2);

    参数的绑定通常称为"Currying"(译注:Currying---“烹制咖喱烧菜”,此处意指对函数或函数对象进行加工修饰操作), "_1"是一个占位符对象,用于表示当函数f通过函数ff进行调用时,函数ff的第一个参数在函数f的参数列表中的位置。第一个参数称为"_1", 第二个参数为"_2",依此类推。例如:

        int f(int, char, double);
        auto frev = bind(f, _3, _2, _1);        // 翻转参数顺序
        int x = frev(1.2, 'c', 7);            // f(7, 'c', 1.2);

    此处,auto关键字节约了我们去推断bind返回的结果类型的工作。

    我们无法使用bind()绑定一个重载函数的参数,我们必须显式地指出需要绑定的重载函数的版本:

        int g(int);
        double g(double);

        auto g1 = bind(g, _1);                // 错误:调用哪一个g() ?
        auto g2 = bind( (double(*)(double))g, _1);    // 正确,但是相当丑陋

    bind()有两种版本:一个如上所述,另一个则是“历史遗留”的版本:你可以显式地描述返回类型。例如:
    
        auto f2 = bind<int> (f, 7, 'c', _1);      // 显式返回类型
        int x = f2(1.2);                    // f(7, 'c', 1.2);

    第二种形式的存在是必要的,并且因为第一个版本((?) "and for a user simplest ",此处请参考原文))无法在C++98中实现。所以第二个版本已经被广泛使用。

    function是一个拥有任何可以以"(...)"符号进行调用的值的类型。特别地,bind的返回结果可以赋值给function类型。function十分易于使用。(译注:更直观地,可以把function看成是一种表示函数的数据类型,就像函数对象一样。只不过普通的数据类型表示的是数据,function表示的是函数这个抽象概念。)例如:

        function<float (int x, int y)> f;        // 构造一个函数对象,它能表示的是一个返回值为float,两个参数为int,int的函数

        struct int_div {        // 构造一个可以使用"()"进行调用的函数对象类型
                float operator() (int x, int y) const { return ((float)x)/y; };
        };

        f = int_div();                    // 赋值
        cout<< f(5,3) <<endl;            // 通过函数对象进行调用
        std::accumulate(b, e, 1, f);        // 完美传递

    成员函数可被看做是带有额外参数的自由函数:

        struct X {
                int foo(int);
        };

        function<int (X*, int)> f;      // 所谓的额外参数,就是成员函数默认的第一个参数,也就是指向调用成员函数的对象的this指针
        f = &X::foo;            // 指向成员函数

        X x;
        int v = f(&x, 5);            // 在对象x上用参数5调用X::foo()
        function<int (int)> ff = std::bind(f, &x, _1);    // f的第一个参数是&x
        v = ff(5);                // 调用x.foo(5)

    function对于回调函数、将操作作为参数传递等十分有用。它可以看做是C++98标准库中函数对象mem_fun_t, pointer_to_unary_function等的替代品。同样的,bind()也可以被看做是bind1st()和bind2nd()的替代品,当然比他们更强大更灵活。

还有一个BLOG里对其有更好的描述

漫话C++0x(四) —- function, bind和lambda

 本文是C++0x系列的第四篇,主要是内容是C++0x中新增的lambda表达式, function对象和bind机制。之所以把这三块放在一起讲,是因为这三块之间有着非常密切的关系,通过对比学习,加深对这部分内容的理解。在开始之间,首先要讲一个概念,closure(闭包),这个概念是理解lambda的基础。下面我们来看看wikipedia上对于计算机领域的closure 的定义:

[html] view plaincopy
  1. A closure (also lexical closure, function closure or function value) is a function together with  
  2. a referencing environment for the non-local variables of that function.  

上面的大义是说,closure是一个函数和它所引用的非本地变量的上下文环境的集合。从定义我们可以得知,closure可以访问在它定义范围之外的变量,也即上面提到的non-local vriables,这就大大增加了它的功力。关于closure的最重要的应用就是回调函数,这也是为什么这里把function, bind和lambda放在一起讲的主要原因,它们三者在使用回调函数的过程中各显神通。下面就为大家一步步接开这三者的神秘面纱。

  • 1. function

        我们知道,在C++中,可调用实体主要包括函数,函数指针,函数引用,可以隐式转换为函数指定的对象,或者实现了opetator()的对象(即C++98中的functor)。C++0x中,新增加了一个std::function对象,std::function对象是对C++中现有的可调用实体的一种类型安全的包裹(我们知道像函数指针这类可调用实体,是类型不安全的)。我们来看几个关于function对象的例子:

    [cpp] view plaincopy
    1. #include < functional>  
    2.    
    3. std::function< size_t(const char*)> print_func;  
    4.    
    5. /// normal function -> std::function object  
    6. size_t CPrint(const char*) { ... }  
    7. print_func = CPrint;  
    8. print_func("hello world"):  
    9.    
    10. /// functor -> std::function object  
    11. class CxxPrint  
    12. {  
    13. public:  
    14.     size_t operator()(const char*) { ... }  
    15. };  
    16. CxxPrint p;  
    17. print_func = p;  
    18. print_func("hello world");  


     

     在上面的例子中,我们把一个普通的函数和一个functor赋值给了一个std::function对象,然后我们通过该对象来调用。其它的 C++中的可调用实体都可以像上面一样来使用。通过std::function的包裹,我们可以像传递普通的对象一样来传递可调用实体,这样就很好解决了类型安全的问题。了解了std::function的基本用法,下面我们来看一些使用过程中的注意事项:

    • (1)关于可调用实体转换为std::function对象需要遵守以下两条原则:
      a. 转换后的std::function对象的参数能转换为可调用实体的参数
      b. 可高用实体的返回值能转换为std::function对象的(这里注意一下,所有的可调用实体的返回值都与返回void的std::function对象的返回值兼容)。
    • (2)std::function对象可以refer to满足(1)中条件的任意可调用实体
    • (3)std::function object最大的用处就是在实现函数回调,使用者需要注意,它不能被用来检查相等或者不相等
    • 2. bind

          bind是这样一种机制,它可以预先把指定可调用实体的某些参数绑定到已有的变量,产生一个新的可调用实体,这种机制在回调函数的使用过程中也颇为有用。C++98中,有两个函数bind1st和bind2nd,它们分别可以用来绑定functor的第一个和第二个参数,它们都是只可以绑定一个参数。各种限制,使得bind1st和bind2nd的可用性大大降低。C++0x中,提供了std::bind,它绑定的参数的个数不受限制,绑定的具体哪些参数也不受限制,由用户指定,这个bind才是真正意义上的绑定,有了它,bind1st和bind2nd就没啥用武之地了,因此C++0x中不推荐使用bind1st和bind2nd了,都是deprecated了。下面我们通过例子,来看看bind的用法:

      [cpp] view plaincopy
      1. #include < functional>  
      2.    
      3. int Func(int x, int y);  
      4. auto bf1 = std::bind(Func, 10, std::placeholders::_1);  
      5. bf1(20); ///< same as Func(10, 20)  
      6.    
      7. class A  
      8. {  
      9. public:  
      10.     int Func(int x, int y);  
      11. };  
      12.    
      13. A a;  
      14. auto bf2 = std::bind(&A::Func, a, std::placeholders::_1, std::placeholders::_2);  
      15. bf2(10, 20); ///< same as a.Func(10, 20)  
      16.    
      17. std::function< int(int)> bf3 = std::bind(&A::Func, a, std::placeholders::_1, 100);  
      18. bf3(10); ///< same as a.Func(10, 100)  


       

      上面的例子中,bf1是把一个两个参数普通函数的第一个参数绑定为10,生成了一个新的一个参数的可调用实体体; bf2是把一个类成员函数绑定了类对象,生成了一个像普通函数一样的新的可调用实体; bf3是把类成员函数绑定了类对象和第二个参数,生成了一个新的std::function对象。看懂了上面的例子,下面我们来说说使用bind需要注意的一些事项:

      • (1)bind预先绑定的参数需要传具体的变量或值进去,对于预先绑定的参数,是pass-by-value的
      • (2)对于不事先绑定的参数,需要传std::placeholders进去,从_1开始,依次递增。placeholder是pass-by-reference的
      • (3)bind的返回值是可调用实体,可以直接赋给std::function对象
      • (4)对于绑定的指针、引用类型的参数,使用者需要保证在可调用实体调用之前,这些参数是可用的
      • (5)类的this可以通过对象或者指针来绑定
    • 3. lambda

          讲完了function和bind, 下面我们来看lambda。有python基础的朋友,相信对于lambda不会陌生。看到这里的朋友,请再回忆一下前面讲的closure的概念,lambda就是用来实现closure的东东。它的最大用途也是在回调函数,它和前面讲的function和bind有着千丝万缕的关系。下面我们先通过例子来看看lambda的庐山真面目:

      [cpp] view plaincopy
      1. vector< int> vec;  
      2. /// 1. simple lambda  
      3. auto it = std::find_if(vec.begin(), vec.end(), [](int i) { return i > 50; });  
      4. class A  
      5. {  
      6. public:  
      7.     bool operator(int i) const { return i > 50; }  
      8. };  
      9. auto it = std::find_if(vec.begin(), vec.end(), A());  
      10.    
      11. /// 2. lambda return syntax  
      12. std::function< int(int)> square = [](int i) -> int { return i * i; }  
      13.    
      14. /// 3. lambda expr: capture of local variable  
      15. {  
      16.     int min_val = 10;  
      17.     int max_val = 1000;  
      18.    
      19.     auto it = std::find_if(vec.begin(), vec.end(), [=](int i) {  
      20.         return i > min_val && i < max_val;   
      21.         });  
      22.    
      23.     auto it = std::find_if(vec.begin(), vec.end(), [&](int i) {  
      24.         return i > min_val && i < max_val;  
      25.         });  
      26.    
      27.     auto it = std::find_if(vec.begin(), vec.end(), [=, &max_value](int i) {  
      28.         return i > min_val && i < max_val;  
      29.         });  
      30. }  
      31.    
      32. /// 4. lambda expr: capture of class member  
      33. class A  
      34. {  
      35. public:  
      36.     void DoSomething();  
      37.    
      38. private:  
      39.     std::vector<int>  m_vec;  
      40.     int               m_min_val;  
      41.     int               m_max_va;  
      42. };  
      43.    
      44. /// 4.1 capture member by this  
      45. void A::DoSomething()  
      46. {  
      47.     auto it = std::find_if(m_vec.begin(), m_vec.end(), [this](int i){  
      48.         return i > m_min_val && i < m_max_val; });  
      49. }  
      50.    
      51. /// 4.2 capture member by default pass-by-value  
      52. void A::DoSomething()  
      53. {  
      54.     auto it = std::find_if(m_vec.begin(), m_vec.end(), [=](int i){  
      55.         return i > m_min_val && i < m_max_val; });  
      56. }  
      57.    
      58. /// 4.3 capture member by default pass-by-reference  
      59. void A::DoSomething()  
      60. {  
      61.     auto it = std::find_if(m_vec.begin(), m_vec.end(), [&](int i){  
      62.         return i > m_min_val && i < m_max_val; });  
      63. }  


       

       上面的例子基本覆盖到了lambda表达的基本用法。我们一个个来分析每个例子(标号与上面代码注释中1,2,3,4一致):

      • (1)这是最简单的lambda表达式,可以认为用了lambda表达式的find_if和下面使用了functor的find_if是等价的
      • (2)这个是有返回值的lambda表达式,返回值的语法如上面所示,通过->写在参数列表的括号后面。返回值在下面的情况下是可以省略的:
        a. 返回值是void的时候
        b. lambda表达式的body中有return expr,且expr的类型与返回值的一样
      • (3)这个是lambda表达式capture本地局部变量的例子,这里三个小例子,分别是capture时不同的语法,第一个小例子中=表示 capture的变量pass-by-value, 第二个小拿出中&表示capture的变量pass-by-reference,第三个小例子是说指定了default的pass-by- value, 但是max_value这个单独pass-by-reference
      • (4)这个是lambda表达式capture类成员变量的例子,这里也有三个小例子。第一个小例子是通过this指针来capture成员变量,第二、三个是通过缺省的方式,只不过第二个是通过pass-by-value的方式,第三个是通过pass-by-reference的

      分析完了上面的例子,我们来总结一下关于lambda表达式使用时的一些注意事项:

      • (1)lambda表达式要使用引用变量,需要遵守下面的原则:
        a. 在调用上下文中的局部变量,只有capture了才可以引用(如上面的例子3所示)
        b. 非本地局部变量可以直接引用
      • (2)使用者需要注意,closure(lambda表达式生成的可调用实体)引用的变量(主要是指针和引用),在closure调用完成之前,必须保证可用,这一点和上面bind绑定参数之后生成的可调用实体是一致的
      • (3)关于lambda的用处,就是用来生成closure,而closure也是一种可调用实体,所以可以通过std::function对象来保存生成的closure,也可以直接用auto

          通过上面的介绍,我们基本了解了function, bind和lambda的用法,把三者结合起来,C++将会变得非常强大,有点函数式编程的味道了。最后,这里再补充一点,对于用bind来生成 function和用lambda表达式来生成function, 通常情况下两种都是ok的,但是在参数多的时候,bind要传入很多的std::placeholders,而且看着没有lambda表达式直观,所以通常建议优先考虑使用lambda表达式。

       为了测试,写了一个小测试程序(GCC )

      [cpp] view plaincopy
      1. #include <iostream>  
      2. #include <functional>  
      3. using namespace std;  
      4. using namespace std::placeholders;  
      5. class MyServer  
      6. {  
      7.     public:  
      8.         MyServer():  
      9.         m_func([] (int ,int) -> int { return 0;})  
      10.         {  
      11.   
      12.         }  
      13.         void BindInterface(function<int (int,int)>  func)  
      14.         {  
      15.             m_func = func;  
      16.         }  
      17.         int Run(int a, int b)  
      18.         {  
      19.             return m_func(a,b);  
      20.         }  
      21.     private:  
      22.         function<int (int,int)> m_func;  
      23. };  
      24.   
      25. class MyClient  
      26. {  
      27.     public:  
      28.         MyClient()  
      29.         {  
      30.   
      31.         }  
      32.         int ProduceRandPair(int a, int b)  
      33.         {  
      34.             return a + b;  
      35.         }  
      36.   
      37. };  
      38.   
      39. int main()  
      40. {  
      41.     int i=3,j=4;  
      42.     MyServer server;  
      43.     cout << server.Run(1,2) << endl;  
      44.     MyClient client;  
      45.     server.BindInterface(bind(&MyClient::ProduceRandPair,client,_1,_2));  
      46.     cout << server.Run(4,5) << endl;  
      47.     server.BindInterface(bind([&i,&j](int x,int y) -> int {return x * y + i++ * ++j;},_1,_2));  
      48.     cout << i << " " <<j <<" "<< server.Run(6,7) <<" " << i << " " <<j <<endl;  
      49.     return 0;  
      50. }  


      0
      9
      4 5 57 3 4

      后面准备玩一玩线程池中利用 function + bind 实现低耦合的调用。参看大神们的BLOG还是很有意思的啊!

    • 0 0
      原创粉丝点击