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

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

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<typename FUNC>
  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<typename O>
  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<A> call(&A::doIt, &a); // 保存 a::doIt指针以便调用
  22. call("Kitty"); // 输出 Hello Kitty!

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

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

标准库的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++函数对象了,只要一个函数对象模板就可以解决问题了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容