• 学习C++11的一些思考和心得(1):lambda,function,bind和委托


     1.lambda表达式

    lanbda表达式简单地来讲就是一个匿名函数,就是没有名称的函数,如果以前有接触过python或者erlang的人都比较熟悉这个,这个可以很方便地和STL里面的算法配合

        std::for_each(list.begin(),list.end(),
                      [ &tmp,id ](struct ComingVesselInfo *info)
                      {
                        if( info->m_nShipID == id)
                        {
                            tmp = info;
                            return ;
                        }
                      }
                     );

    这个是我在项目里面使用的一段代码,如果没有lambda,的代码则是这样的:

        for ( auto iter = list.begin();
               iter != list.end();
               ++iter)
        {
            if ( iter->m_nShipID == id)
            {
                tmp = iter;
            }
        }

    从上面的代码比较并没有看出lambda有什么用,反而丧失了一点代码的可读性,但是我觉得lambda的引入不是我们可以在函数内直接很方便地写函数,而是可以很好地跟STL algorithm库配合,在我们的项目中,我很少使用到STL algorithm库,因为大部分算法函数都有下面的两个问题:

    1.无法获取返回值

    2.无法传入其他的参数(比如上面的for_each的例子,如果没有lambda,并没有办法把tmp和id传入)

    lambda的引入解决了我在项目中使用STL的algorithm的顾虑

    顺便说下,上面的auto也是C++11的新标准,就是自动推导右边数据的类型

    2.function和bind以及委托


    在C#里面,我们经常会看到使用委托的,C#的委托语法是这样的:

    public delegate void Ticket();

    这样的话,我们只要声明一个Ticket t,这个只要符合functionname()这个形式的参数,不管是static 函数还是类成员函数,都可以赋值给t,并且可以通过t()来调用该函数或者成员函数

    很多学C#的程序员一看这个就简单地把委托认为是C++里面的函数指针,其实这个是错的,委托和函数指针的区别在于,委托是有状态的,更类似与闭包,而函数指针只是一个地址,是没有状态的,单纯的函数指针无法根据我们的需求去传入外部的状态(也就是无法根据需求去改变传入参数的个数和参数的类型)

    在C++里面,我们只能通过这样来指向一个成员函数的指针:

    int (simple:: *pfn) (int)  = simple::fn;

    这样地话我们才能把一个类成员函数指针指向一个类的成员函数,但是这样明显有两个缺点:

    1.我们需要在函数调用的时候才能传入参数,无法在函数确定要指向哪一个类成员函数的时候指定参数

    2.最重要的一点,我们无法把pfn认识指向一个functionname(int)的类成员函数,我们只能再次定义一个函数指针

    在C++里面实际上也是有类似于委托的功能,利用类的成员变量来保存我们的状态,然后重载operator(),就可以达到我们所说的闭包的效果

    class Add
    {
    public:
        Add(int i,int j)
        {
            m_i = i;
            m_j = j;
        }
    
        int operator()()
        {
            return m_i + m_j;
        }
    
    protected:
        int m_i;
        int m_j;
    };

    这样我们就可以这样使用这个Add类

        Add add(1,2);
        cout<<add()<<endl;

    但是很明显地,这样做我们每次都需要根据需求去创建一个class,实际上并没有比我们根据需求来写函数方便地多少,还不如直接使用lambda,在C++ 11则解决了这个问题,引入了boost的function和bind

    class calc
    {
    public:
        calc(int base)
        {
            m_base = base;
        }
    
        int Add(int j)
        {
            m_base += j;
            return m_base;
        }
    
        int sub(int j)
        {
            m_base -= j;
            return m_base;
        }
    protected:
        int m_base;
    };
    int main(int argc,char *argv[])
    {
        calc *c = new calc(10);
        tr1::function<int ()> fun;
        
        fun = tr1::bind(&calc::Add,c,3);
        cout<<fun()<<endl;
    
        tr1::function<int (int)> fun2;
        fun2 = tr1::bind(&calc::sub,c,tr1::_1);
        cout<<fun2(3);
    
    }

    function和bind的引入解决了我们无法使用C#里面委托的问题,并且既可以提前绑定参数,又可以延后绑定参数,而且可以跟任何类成员函数配合

    看起来是很方便的,但是别忘记了C++里面的一个问题,就是C++没有内存回收的功能,如果我们bind的类被释放掉了,那么会怎么样??

    如果我们main函数改成下面这样调用:

        calc *c = new calc(10);
        tr1::function<int ()> fun;
        
        
        fun = tr1::bind(&calc::Add,c,3);
        delete c;
        c = NULL;
    
        cout<<fun()<<endl;

    那么我们调用fun()的时候会发生什么事情??

    我们无法得知计算得结果,因为此时的c已经被delete掉了,虽然可以依然正常调用Add函数(如果对C++比较熟悉的应该知道为什么可以正常调用Add),但是内部我们保存的"状态"不见了,也随着delete动作一起消失了,留下的是一串随机的数

    但是如果我们把下面的代码改成这样:

    tr1::function<int ()> fun2;
    {
      calc d(12);
      fun2 = tr1::bind(&calc::Add,d,40);
    }

    fun2();

    那么即使在d释放掉以后,我们依然可以正常地调用fun2

    根据我的测试,在我们调用bind的时候,实际上将整个类的当前"状态"都复制了过去,而且在VS2010的平台上,我自己测试了下,调用了9次的复制构造函数,如果我们不好好处理这个,对于性能将会是巨大的损失

    当时标准库的设计者也没有那么地傻,所以bind也可以传入指针:

    fun2 = tr1::bind(&calc::Add,&d,40);

    当然这个又涉及到类的状态保存的问题,类销毁了怎么办??

    所以说C++的function和bind的再能模仿,也无法模仿到C#的委托,毕竟C++里面没有人帮我们管理内存,而C#程序员似乎不用关心这些,C++程序员永远离不开内存管理的话题

    参考资料:

    以boost::function和boost:bind取代虚函数

    http://www.boost.org/doc/libs/1_54_0/doc/html/function.html

    http://www.boost.org/doc/libs/1_54_0/libs/bind/bind.html

    http://msdn.microsoft.com/zh-cn/library/vstudio/dd293608.aspx

  • 相关阅读:
    利用beautifulsoup4解析Kindle笔记
    对流媒体传输关键指标作简单预测
    Linux上使用Windows软件
    Tex家族关系
    数学基础-概率论05(统计推断-分布拟合检验)
    数学基础-概率论04(统计推断-参数假设检验)
    数学基础-概率论03(统计推断-参数估计)
    数学基础-概率论01(离散型分布)
    数学基础-概率论02 (连续型分布)
    Calibre中使用DeDRM插件进行Kindle电子书解锁
  • 原文地址:https://www.cnblogs.com/linyilong3/p/3371339.html
Copyright © 2020-2023  润新知