c++ 闭包 boost::bind 函数对象 仿函数

c++ 闭包 boost::bind 函数对象 仿函数

Posted on 2014-12-14 12:20 bw_0927 阅读(481) 评论(0) 编辑 [收藏](javascript:void(0))

http://microcai.org/2013/07/20/closure.html

可以为类类型的对象重载函数调用操作符,定义了调用操作符的类,其对象称之为函数对象(function object),即它们的行为类似函数对象。

仿函数(Functor、Function Object)

传递给STL算法的函数型参数(functional arguement)不一定要是函数,可以是行为类似于函数的对象,即Function Object或者Functor。

STL中大量运用了Function Object,也提供了很多预先定义的Function Object。

在大多数函数式语言中,不允许函数有副作用,即函数不能访问或改变外部状态(比如全局变量),这样做极大方便了单元测试和bug 定位以及并发,但是在一些函数式语言中对函数副作用的要求稍稍放宽了限制,

引入了词法闭包(lexical closure),允许函数可以保留自己的context, 以便设计出传出值是函数的函数。

C++ 闭包 探秘

Posted on 20 Jul 2013

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

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

每次使用 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 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( 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 爸爸提供了一套强大的宏, 将状态机的实现给自动化了.

=============

http://developer.51cto.com/art/201002/183522.htm

在C++编程语言中,有很多功能都与C语言相通,比如指针的应用等等。在这里我们介绍的则是一种类似于函数指针的C++函数对象的相关介绍。C++函数对象不是函数指针(有状态)。但是,在程序代码中,它的调用方式与函数指针一样,后面加个括号就可以了。这是入门级的随笔,说的是函数对象的定义,使用,以及与函数指针,成员函数指针的关系。

C++函数对象实质上是一个实现了operator()--括号操作符--的类。例如:

  1. class Add
  2. {
  3. public:
  4. int operator()(int a, int b)
  5. {
  6. return a + b;
  7. }
  8. };
  9. Add add; // 定义函数对象
  10. cout << add(3,2); // 5

函数指针版本就是:

  1. int AddFunc(int a, int b)
  2. {
  3. return a + b;
  4. }
  5. typedef int (*Add) (int a, int b);
  6. Add add = &AddFunc;
  7. cout << add(3,2); // 5

呵呵,除了定义方式不一样,使用方式可是一样的。都是:

  1. cout << add(3,2);

既然C++函数对象与函数指针在使用方式上没什么区别,那为什么要用函数对象呢?很简单,函数对象可以携带附加数据,而指针就不行了。下面就举个使用附加数据的例子:

  1. class less
  2. {
  3. public:
  4. less(int num):n(num){}
  5. bool operator()(int value)
  6. {
  7. return value < n;
  8. }
  9. private:
  10. int n;
  11. };

使用的时候:

  1. less isLess(10);
  2. cout << isLess(9) << " " << isLess(12); // 输出 1 0

这个例子好象太儿戏了,换一个:

  1. const int SIZE = 5;
  2. int array[SIZE] = { 50, 30, 9, 7, 20};
  3. // 找到小于数组array中小于10的第一个数的位置
  4. int * pa = std::find_if(array, array + SIZE, less(10)); //创建一个临时的函数对象(暂且叫做tmpObj)作为形参,find_if内部依次调用tmpObj(50), tmpObje(30).....
    // pa 指向 9 的位置
  5. // 找到小于数组array中小于40的第一个数的位置
  6. int * pb = std::find_if(array, array + SIZE, less(40)); 
    // pb 指向 30 的位置

/*

find_if的内部伪代码实现

template``<``class InputIterator, class Predicate>

InputIterator find_if ( InputIterator first, InputIterator last, Predicate pred )

{

for ( ; first!=last ; first++ ) if ( pred(*first) ) break``;

return first;

}

*/

这里可以看出C++函数对象的方便了吧?可以把附加数据保存在函数对象中,是函数对象的优势所在。
它的弱势也很明显,它虽然用起来象函数指针,但毕竟不是真正的函数指针。在使用函数指针的场合中,它就无能为力了。例如,你不能将函数对象传给qsort函数!因为它只接受函数指针。

要想让一个函数既能接受函数指针,也能接受函数对象,最方便的方法就是用模板。如:

  1. template
  2. int count_n(int* array, int size, FUNC func)
  3. {
  4. int count = 0;
  5. for(int i = 0; i < size; ++i)
  6. if(func(array[i]))
  7. count ++;
  8. return count;
  9. }

这个函数可以统计数组中符合条件的数据个数,如:

  1. const int SIZE = 5;
  2. int array[SIZE] = { 50, 30, 9, 7, 20};
  3. cout << count_n(array, SIZE, less(10)); // 2
  4. 用函数指针也没有问题:
  5. bool less10(int v)
  6. {
  7. return v < 10;
  8. }
  9. cout << count_n(array, SIZE, less10); // 2

另外,C++函数对象还有一个函数指针无法匹敌的用法:可以用来封装类成员函数指针!因为函数对象可以携带附加数据,而成员函数指针缺少一个类实体(类实例)指针来调用,因此,可以把类实体指针给函数对象保存起来,就可以用于调用对应类实体成员函数了。

  1. template
  2. class memfun
  3. {
  4. public:
  5. memfun(void(O::f)(const char), O* o): pFunc(f), pObj(o){}
  6. void operator()(const char* name)
  7. {
  8. (pObj->*pFunc)(name);
  9. }
  10. private:
  11. void(O::pFunc)(const char);
  12. O* pObj;
  13. };
  14. class A
  15. {
  16. public:
  17. void doIt(const char* name)
  18. { cout << "Hello " << name << "!";}
  19. };
  20. A a;
  21. memfun call(&A::doIt, &a); // 保存 a::doIt指针以便调用
  22. call("Kitty"); // 输出 Hello Kitty!

大功告成了,终于可以方便保存成员函数指针,以备调用了。

  • C++常量引用正确应用方法
  • C++链栈模板应用代码解读
  • C++剪切板常用应用技巧分享
  • C++ Doxygen实现功能分享
  • C++ sprintf格式化解决方法详解

不过,现实是残酷的。函数对象虽然能够保有存成员函数指针和调用信息,以备象函数指针一样被调用,但是,它的能力有限,一个函数对象定义,最多只能实现一个指定参数数目的成员函数指针。

标准库的mem_fun就是这样的一个函数对象,但是它只能支持0个和1个参数这两种成员函数指针。如 int A::func()或void A::func(int)、int A::func(double)等等,要想再多一个参数如:int A::func(int, double),不好意思,不支持。想要的话,只有我们自已写了。

而且,就算是我们自已写,能写多少个?5个?10个?还是100个(这也太恐怖了)?

好在boost库提供了boost::function类,它默认支持10个参数,最多能支持50个函数参数(多了,一般来说这够用了。但它的实现就是很恐怖的:用模板部份特化及宏定义,弄了几十个模板参数,偏特化(编译期)了几十个函数对象。

C++0x已经被接受的一个提案,就是可变模板参数列表。用了这个技术,就不需要偏特化无数个C++函数对象了,只要一个函数对象模板就可以解决问题了。

你可能感兴趣的:(c++ 闭包 boost::bind 函数对象 仿函数)