C++11 lambda表达式

缘起

最近在读boost.asio的实例代码,其中有使用c++11标准的lambda表达式。都有些看不懂代码的意思。异步调用注册回调函数时,直接使用lambda表达式来制作。在C语言的libEvent通讯库,当一个数据包达到,一个连接建立这样的回调,都是通过函数指针来制作。ACE框架是通过接口类,继承对象之后编写回调函数来做。而在之前boost.asio版本的用法中,基本上都是使用的boost.bind,boost.function库模拟了libevent的函数指针方式。有了匿名函数之后可以更加节约代码,直接在调用的时候,将回调函数写完。这些写法在java、js、c#语言里面很早就有这样的支持。下面是js中通过http模块请求数据的实例:

var http = require('http'), 
  urls = ['www.baidu.com','www.10jqka.com.cn','www.duokan.com']; 
function fetchPage(url){ 
  var start = new Date(); 
  http.get({host:url},function(res){ 
    console.log("Got response from:" + url); 
    console.log("Request took:",new Date() - start, "ms"); 
  }); 
} 
for(var i=0; i

匿名函数好处

1.lambda表达式是C++里面的闭包,他也有访问自己域上面的变量的能力。闭包可以理解成“函数绑定了一堆变量”;而相对类比的是类:“类是成员变量绑定了一堆函数”。
2.写起来将会更加灵活小巧,比如说,lambda表达式作为一个函数指针,能被传递给框架代码做一些特殊处理,而且在处理的过程中,我们可以很方便的使用到调用时候的上下文变量,真是左右逢源。(如果使用libEvent写类似代码,可能在设计的开始就要确定好自定义的struct。在注册事件时将数据填充好并且传递给libEvent,当libEvent有事件到达的时候,他将会把这个struct指针传回来。)

代码差异

下面我们来通过两段代码来对比一下c++11标准、c++标准boost.bind方式的代码,在编写boost.asio的accept回调写法的差异:
c++ 11 标准:

void do_accept()
    {
        acceptor_.async_accept(socket_,
            [this](boost::system::error_code ec)
        {
            if (!ec)
            {
                std::make_shared(std::move(socket_))->start();
            }

            do_accept();
        });
    }

c++ 标准:

  void start_accept()
  {
    session* new_session = new session(io_service_);
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }
  void handle_accept(session* new_session,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_session->start();
    }
    else
    {
      delete new_session;
    }

    start_accept();
  }

两段代码达到了相同的效果。

cppreference_Lambda_表达式

lambda学习笔记:

构造一个闭包:一个无名字函数对象能访问范围中的变量。注意这个lambda表达式是一个闭包。它能访问到自己函数域外部的变量。但是需要通过[capture-list]里面定义lambda表达式的访问方式。

语法

1.[ capture-list ] ( params ) mutable(optional) constexpr(optional)(c++17) exception attribute -> ret { body }
2.[ capture-list ] ( params ) -> ret { body }
3.[ capture-list ] ( params ) { body }
4.[ capture-list ] { body }

1.第一个是全定义模式;
2.申明一个常量lambda:被captured的对象是复制的不能被修改;
3.简略返回值模式:闭包返回值类型依据下面两条规则:
3.1.如果这个body没有任何内容,只有依据return (表达式);时,返回类型就依据返回的类型;(until C++14)
否则将返回void
3.2.返回类型将来自于 return statements随返回类型申明的推导函数。
4.省略掉参数表模式;

解释

mutable

允许body去修改通过值捕获参数内容。

#include 

int main()
{
    int a = 1, b = 1, c = 1;

    auto m1 = [a, &b, &c]() mutable {
        auto m2 = [a, b, &c]() mutable {
            std::cout << a << b << c << '\n';
            a = 4; b = 4; c = 4;
        };
        a = 3; b = 3; c = 3;
        m2();
    };

    a = 2; b = 2; c = 2;

    m1();                             // calls m2() and prints 123
    std::cout << a << b << c << '\n'; // prints 234
}

如果将mutable去掉

    auto m1 = [a, &b, &c]()  {
        auto m2 = [a, b, &c]()  {
            std::cout << a << b << c << '\n';
            a = 4; b = 4; c = 4;
        };
        a = 3; b = 3; c = 3;
        m2();
    };

将会直接出现这些报错:

1>f:\learn\c++\test_boost\main.cpp(94): error C3491: “a”: 无法在非可变 lambda 中修改通过复制捕获
1>f:\learn\c++\test_boost\main.cpp(94): error C3491: “b”: 无法在非可变 lambda 中修改通过复制捕获
1>f:\learn\c++\test_boost\main.cpp(96): error C3491: “a”: 无法在非可变 lambda 中修改通过复制捕获

在capture-list理解完之后将会更加加深映像。其实定义了mutable之后,将会默认直接把外部的变量都做一个copy的副本来做修改。

exception

抛出异常选项

attributes

lambda属性

capture-list

0~n由,分割符分割的抓取列表,capture-list的开头是使用一个capture-default。下面列举了capture-default的一些实例:

  • [a,&b] 定义a变量传值方式使用,b变量是引用方式使用;
  • [this] 将this指针传值方式加以使用
  • [&,x] x 显示地以传值方式加以使用。其余变量以引用方式加以使用。
  • [=,&z] z 显示地以引用方式加以使用。其余变量以传值方式加以使用。
  • [] 不抓取

读到这里我capture其实是在定义闭包访问scope中变量的方式。不像Lua闭包一样的,在闭包里面如果要访问scope中的变量的时候,需要在这个里面定义访问的规则。

看完这个部分之后大概能看懂了这个代码了:

    void do_read()
    {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t length)
        {
            if (!ec)
            {
                do_write(length);
            }
        });
    }

[this,self]这个的意思是将this和self这个share_ptr指针,以传值方式传递给闭包来使用。

params

就是匿名函数的参数表。

ret

定义lambda表达式的返回值

body

函数实现。

总结:

使用lambda表达式来优化程序还是很有必要。特别是在异步处理的时候,closure在写异步调用能更加符合人的思维方式。

你可能感兴趣的:(C++)