c++

C++ 闭包 探秘

Posted on July 20, 2013

我经常说协程, 说协程的时候又经常会提到闭包. 还有我常说, boost::bind 是神器 归根结底, 神的是 “闭包”

没有闭包, 就无法实现 asio 协程 (注意, 我说的是 ASIO的协程, 并不是通常意义上 setjmp/longjmp 或者 CreateFiber 又或者 boost.context 创建的协程)

每次使用 bind , 你就创建了一个闭包.

简单的来说, 闭包就是带状态的函数

一个函数, 带上了一个状态, 就变成了闭包了. 什么叫 “带上状态” 呢? 意思是这个闭包有属于自己的变量, 这些个变量的值是创建闭包的时候设置的, 并在调用闭包的时候, 可以访问这些变量.

函数是代码, 状态是一组变量

将代码和一组变量捆绑 (bind) , 就形成了闭包

内部包含 static 变量的函数, 不是闭包, 因为这个 static 变量不能捆绑. 你不能捆绑不同的 static 变量. 这个在编译的时候已经确定了. 闭包的状态捆绑, 必须发生在运行时.

闭包的实现

C++ 里使用闭包有3个办法

重载 operator()

因为闭包是一个函数+一个状态, 这个状态通过 隐含的 this 指针传入. 所以 闭包必然是一个函数对象. 因为成员变量就是极好的用于保存状态的工具, 因此实现 operator() 运算符重载, 该类的对象就能作为闭包使用. 默认传入的 this 指针提供了访问成员变量的途径.

事实上, lambda 和 bind 的原理都是这个.

lambda

c++11 里提供的 lambda表达式就是很好的语法糖. 其本质和手写的函数对象没有区别.

boost::bind/std::bind

标准库提供的 bind 是更加强大的语法糖, 将手写需要很多很多代码的闭包, 浓缩到一行 bind 就可以搞定了.

闭包的用法

闭包是一个强大的武器, 好好使用能事半功倍

用做回调函数

闭包的第一个用处就是作为回调函数, 消除 C 语言里回调函数常见的 *this 指针.

解耦合

@hyq 对这个问题这么看的

我写了一个用于音频播放的类,我把play(int16_t* data, int size)这个函数直接交给那些需要播放音频的模块

但是,如果我某天想要换另外一个音频播放的类,这个类的接口变成了play(int channel, int16_t* data, int size),那么我可以用boost::bind将第一个参数先行绑定了

用boost::bind可以把不同模块之间可能不兼容的接口给拼接起来

通过兼容的函数对象, 而不是函数指针, 放宽了函数签名的要求.

信息隔离

avbot 的验证码实现里, 有一个功能是 报告验证码错误 的功能. 这个实现就用到了闭包进行信息隔离.

static void vc_code_decoded(boost::system::error_code ec, std::string provider, std::string vccode, boost::function<void()> reportbadvc, avbot & mybot)
{
	BOOST_LOG_TRIVIAL(info) << console_out_str("使用 ") <<  console_out_str(provider) << console_out_str(" 成功解码验证码!");

	if (provider == "IRC/XMPP 好友辅助验证码解码器")
		mybot.broadcast_message("验证码已输入");

	mybot.feed_login_verify_code(vccode, reportbadvc);
	need_vc = false;
}

static void on_verify_code(const boost::asio::const_buffer & imgbuf, avbot & mybot, decaptcha::deCAPTCHA & decaptcha)
{
	const char * data = boost::asio::buffer_cast<const char*>( imgbuf );
	size_t	imgsize = boost::asio::buffer_size( imgbuf );
	std::string buffer(data, imgsize);

	BOOST_LOG_TRIVIAL(info) << "got vercode from TX, now try to auto resovle it ... ...";

	decaptcha.async_decaptcha(
		buffer,
		boost::bind(&vc_code_decoded, _1, _2, _3, _4, boost::ref(mybot))
	);
}

async_decaptcha 的回调函数 vc_code_decoded 的第四个参数, 是一个闭包, 如果验证码有错, 直接调用这个闭包就可以完成汇报. 至于回报所需要的一些相关信息 如 验证码提供商 返回的验证码ID 等等信息, 都已经封装在这个闭包里了. 用户无需知道 汇报一个错误识别的验证码到底需要多少信息 , 无需知道. 通过闭包, 验证码识别代码将这些信息全部隐藏起来了, 甚至两一个 HANDLE 都无需提供.

用作协程

多次调用能保留状态的闭包就可以用于实现是协程.

不仅仅要修改状态, 还要根据状态实现不同的行

保留状态的目的就是要后续调用的时候根据前面的状态选择不同的执行路径 那么这样做就是协程了. 虽然手工编写状态代码并不难, 但是很麻烦, 繁琐.

因此 ASIO 爸爸提供了一套强大的宏, 将状态机的实现给自动化了.

Comments