zoukankan      html  css  js  c++  java
  • C++11新特性之九——function、bind以及lamda表达式总结

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

    A closure (also lexical closure, function closure or function value)isfunction together with
    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对象的例子:

        1. #include < functional>  
             
          std::function< size_t (const char*) > print_func;  
             
          /// normal function -> std::function object  
          size_t CPrint(const char*) { ... }  
          print_func = CPrint;  
          print_func("hello world"):  
             
          /// functor -> std::function object  
          class CxxPrint  
          {  
          public:  
              size_t operator()(const char*) { ... }  
          };  
          CxxPrint p;  
          print_func = p;  
          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的用法:

        1. #include < functional>  
             
          int Func(int x, int y);  
          auto bf1 = std::bind(Func, 10, std::placeholders::_1);  
          bf1(20); ///< same as Func(10, 20)  
             
          class A  
          {  
          public:  
              int Func(int x, int y);  
          };  
             
          A a;  
          auto bf2 = std::bind(&A::Func, a, std::placeholders::_1, std::placeholders::_2);  
          bf2(10, 20); ///< same as a.Func(10, 20)  
             
          std::function< int(int)> bf3 = std::bind(&A::Func, a, std::placeholders::_1, 100);  
          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的庐山真面目:

        1. vector< int> vec;  
          /// 1. simple lambda  
          auto it = std::find_if(vec.begin(), vec.end(), [](int i) { return i > 50; });  
          class A  
          {  
          public:  
              bool operator(int i) const { return i > 50; }  
          };  
          auto it = std::find_if(vec.begin(), vec.end(), A());  
             
          /// 2. lambda return syntax  
          std::function< int(int)> square = [](int i) -> int { return i * i; }  
             
          /// 3. lambda expr: capture of local variable  
          {  
              int min_val = 10;  
              int max_val = 1000;  
             
              auto it = std::find_if(vec.begin(), vec.end(), [=](int i) {  
                  return i > min_val && i < max_val;   
                  });  
             
              auto it = std::find_if(vec.begin(), vec.end(), [&](int i) {  
                  return i > min_val && i < max_val;  
                  });  
             
              auto it = std::find_if(vec.begin(), vec.end(), [=, &max_value](int i) {  
                  return i > min_val && i < max_val;  
                  });  
          }  
             
          /// 4. lambda expr: capture of class member  
          class A  
          {  
          public:  
              void DoSomething();  
             
          private:  
              std::vector<int>  m_vec;  
              int m_min_val;  
              int m_max_va;  
          };  
             
          /// 4.1 capture member by this  
          void A::DoSomething()  
          {  
              auto it = std::find_if(m_vec.begin(), m_vec.end(), [this](int i){  
                  return i > m_min_val && i < m_max_val; });  
          }  
             
          /// 4.2 capture member by default pass-by-value  
          void A::DoSomething()  
          {  
              auto it = std::find_if(m_vec.begin(), m_vec.end(), [=](int i){  
                  return i > m_min_val && i < m_max_val; });  
          }  
             
          /// 4.3 capture member by default pass-by-reference  
          void A::DoSomething()  
          {  
              auto it = std::find_if(m_vec.begin(), m_vec.end(), [&](int i){  
                  return i > m_min_val && i < m_max_val; });  
          }  

           

        上面的例子基本覆盖到了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表达式。

  • 相关阅读:
    【leetcode】Binary Search Tree Iterator
    【leetcode】Palindrome Partitioning II
    【leetcode】Best Time to Buy and Sell Stock III
    【leetcode】Best Time to Buy and Sell Stock II
    【leetcode】Longest Consecutive Sequence
    【leetcode】Factorial Trailing Zeroes
    【leetcode】Simplify Path
    【leetcode】Generate Parentheses
    【leetcode】Combination Sum II
    【leetcode】Combination Sum
  • 原文地址:https://www.cnblogs.com/yyxt/p/4253088.html
Copyright © 2011-2022 走看看