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

A closure (also lexical closure, function closure or function value)is a function 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, , std::placeholders::_1);
        bf1(); ///< 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(, ); ///< same as a.Func(10, 20) std::function< int(int)> bf3 = std::bind(&A::Func, a, std::placeholders::_1, );
        bf3(); ///< 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 > ; });
        class A
        {
        public:
        bool operator(int i) const { return i > ; }
        };
        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 = ;
        int 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(), [&](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表达式。

C++11新特性之九——function、bind以及lamda表达式总结的更多相关文章

  1. C++11新特性之二——std::bind std::function 高级用法

    /* * File: main.cpp * Author: Vicky.H * Email: eclipser@163.com */ #include <iostream> #includ ...

  2. C++11新特性应用--实现延时求值(std::function和std::bind)

    说是延时求值,注意还是想搞一搞std::function和std::bind. 之前博客<C++11新特性之std::function>注意是std::function怎样实现回调函数. ...

  3. C++11新特性总结 (二)

    1. 范围for语句 C++11 引入了一种更为简单的for语句,这种for语句可以很方便的遍历容器或其他序列的所有元素 vector<int> vec = {1,2,3,4,5,6}; ...

  4. [转载] C++11新特性

    C++11标准发布已有一段时间了, 维基百科上有对C++11新标准的变化和C++11新特性介绍的文章. 我是一名C++程序员,非常想了解一下C++11. 英文版的维基百科看起来非常费劲,而中文版维基百 ...

  5. c++ 11 线程池---完全使用c++ 11新特性

    前言: 目前网上的c++线程池资源多是使用老版本或者使用系统接口实现,使用c++ 11新特性的不多,最近研究了一下,实现一个简单版本,可实现任意任意参数函数的调用以及获得返回值. 0 前置知识 首先介 ...

  6. 在C++98基础上学习C++11新特性

    自己一直用的是C++98规范来编程,对于C++11只闻其名却没用过其特性.近期因为工作的需要,需要掌握C++11的一些特性,所以查阅了一些C++11资料.因为自己有C++98的基础,所以从C++98过 ...

  7. C++11新特性之一——Lambda表达式

    C++11新特性总结可以参考:http://www.cnblogs.com/pzhfei/archive/2013/03/02/CPP_new_feature.html#section_6.8 C++ ...

  8. C++ 11学习和掌握 ——《深入理解C++ 11:C++11新特性解析和应用》读书笔记(一)

    因为偶然的机会,在图书馆看到<深入理解C++ 11:C++11新特性解析和应用>这本书,大致扫下,受益匪浅,就果断借出来,对于其中的部分内容进行详读并亲自编程测试相关代码,也就有了整理写出 ...

  9. C++11新特性总结 (一)

    1. 概述 最近在看C++ Primer5 刚好看到一半,总结一下C++11里面确实加了很多新东西,如果没有任何了解,别说自己写了,看别人写的代码估计都会有些吃力.C++ Primer5是学习C++1 ...

随机推荐

  1. [uart]2.tty和uart的函数调用流程

    以下是在include/uapi/linux/tty.h中定义了现有的线规号,如果需要定义新的,则需要在后面添加新的 /* line disciplines */ #define N_TTY 0 #d ...

  2. spring4和hibernate4整合的步骤

    基本的整合步骤如下: 由于在spring中可以直接实现自动装配bean对象,所以可以直接将hibernate中的配置属性移植过来: 1. 装配dataSource对象 <!-- 配置数据源 -- ...

  3. scp基本使用方法

    scp基本使用方法: scp用于在两台电脑之间进行数据的copy,形式如下:  第一种, scp [-r] 文件/文件夹  user@host:dir ,需要输入密码.  第二种, scp [-r] ...

  4. warning LNK4099: PDB 原因及解决方案

    0x00 现象及原因举例: warning LNK4099: PDB 'wxbase30ud.pdb' was not found with 'wxbase30ud.lib(any.obj)'使用VC ...

  5. jquery 新建的元素事件绑定问题研究[转]

    原文:http://www.cnblogs.com/linzheng/archive/2010/10/17/1853568.html js的事件监听跟css不一样,css只要设定好了样式,不论是原来就 ...

  6. jQuery方法笔记

    .clone() $(selector).clone(includeEvents) $(this).clone(true) //boolean值,true/false分别对饮是否复制元素的所有事件处理

  7. 更改HDFS权限

    hdfs dfs -chmod -R 755 / 之前执行过这条语句,但是总是提示: 15/05/21 08:10:18 WARN util.NativeCodeLoader: Unable to l ...

  8. Graying the black box: Understanding DQNs

    Zahavy, Tom, Nir Ben-Zrihem, and Shie Mannor. "Graying the black box: Understanding DQNs." ...

  9. wex5中集成的mysql数据库 打开时一闪而过 报错

    在进程中kill mysql.exe 重新启动即可

  10. unity3d绘画手册-------灯光之反射及各个参数解释

    下面说一下Reflection Probe, 大家都知道:当使用标准着色器时,每一个材质都会具有一定程度的镜面反射(specularity)和金属反射 (metalness)属性,在没有强大的硬件来处 ...