最近在读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表达式是一个闭包。它能访问到自己函数域外部的变量。但是需要通过[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.省略掉参数表模式;
允许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的副本来做修改。
抛出异常选项
lambda属性
0~n由,分割符分割的抓取列表,capture-list的开头是使用一个capture-default。下面列举了capture-default的一些实例:
读到这里我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指针,以传值方式传递给闭包来使用。
就是匿名函数的参数表。
定义lambda表达式的返回值
函数实现。
使用lambda表达式来优化程序还是很有必要。特别是在异步处理的时候,closure在写异步调用能更加符合人的思维方式。