【C++11】新特性——std::function

用过C#的人,一般都知道委托和事件。
如果没有用过C#,我在这里简单解释一下委托,如果用过了,下面可以skip。

委托是一个方法声明,我们知道,在C语言中,可以通过函数指针表示一个地址的CALL方法,委托在C#中也差不多是干这样的工作。
但是委托有一些不同,主要的地方就是,在C++中, 函数指针并不是“面向对象”的 ,比如,我们有一个类CTest,类中有一个成员方法foo,此时如果我们要通过函数指针的方式来调用foo的话,因为foo是类CTest的成员,我们需要CTest的实例this指针。

比方说:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. class CTest  
  2. {  
  3.     public:  
  4.         void foo(int i) {}  
  5. };  
它的成员函数指针应该这样写:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void (CTest::*FuncFoo)(int);  
如果这个成员是const修饰的,则这样:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void (CTest::*FuncFoo)(intconst;  

调用怎么办呢?这样:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. CTest* pThis = new...;  
  2. FuncFoo pThis_foo = &CTest::foo;  
  3. (pThis->*pThis_foo)(123);  
如果你这样写:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. pThis->*pThis_foo(123);  
是会出问题的,因为->*这个运算符的优先级很低。。。。

如果CTest不是在HEAP上的,是在stack上的呢?
这样:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. CTest pThis;  
  2. (pThis.*pThis_foo)(123);  

这些奇葩的语法,->*、.*这种神奇的运算符真是蛋疼。
而有时候我们又无法逃避,比如启动线程的时候,我们一般从一个static函数通过这种sb的语法,把类this当作参数传过去,然后跑过去类的成员函数。

但是在C#中, 委托则是面向对象的 ,你可以用委托表示一个类实例的成员函数指针。
比如:
[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. namespace ConsoleApplication1  
  2. {  
  3.     public delegate void CallbackFunc(int i);  
  4.     //void (callback*)(int i);  
  5.   
  6.     class CNewTest  
  7.     {  
  8.         private static readonly string _str = "this CNewTest.";  
  9.   
  10.         public void MemberCallback(int i)  
  11.         {  
  12.             System.Diagnostics.Debug.WriteLine(i.ToString());  
  13.             System.Diagnostics.Debug.WriteLine(_str);  
  14.         }  
  15.     }  
  16.   
  17.     class Program  
  18.     {  
  19.         static void StaticCallback(int i)  
  20.         {  
  21.             System.Diagnostics.Debug.WriteLine(i.ToString());  
  22.         }  
  23.   
  24.         static void Main(string[] args)  
  25.         {  
  26.             CallbackFunc StaticCbF = new CallbackFunc(StaticCallback);  
  27.             StaticCbF(123); //Call to static void StaticCallback(int i)  
  28.   
  29.             CNewTest c = new CNewTest();  
  30.             CallbackFunc MembCbF = new CallbackFunc(c.MemberCallback);  
  31.             MembCbF(1234); //Call to c->MemberCallback  
  32.   
  33.             Console.ReadKey();  
  34.         }  
  35.     }  
  36. }  
输出:
123
1234
this CNewTest.

也就是,在C#中,只要方法适应委托声明,那都可以call过去,C#为我们自动隐藏了this指针。
所以,就在C#中诞生了事件,一个提供者对象通过定义一个委托,然后把委托声明为事件,此时其他监听者对象就可以把这个事件跟自己的成员函数挂钩上来,然后等待提供者在需要的时候调用这个委托,则调用到了成员函数,就是触发了事件。

为什么声明为事件,直接暴露委托不是可以了么?
这是一个逻辑问题,如果直接暴露委托,那订阅者是可以主动触发委托的,订阅者可以是主动的,还“订阅”个屁啊。
如果用事件,则订阅者只能等待提供者来触发委托,此时订阅者,只能是“被动”的。

这个概念,用C++的来理解,也就是:
类A有一个public的ptr、ptr_this,此ptr保存了一个成员函数指针,ptr_this保存ptr的类实例。
类B把这个ptr改成他自己的,ptr_this改成它的this,适应这个成员函数指针的声明。
类A在某些工作搞定后,检查一下ptr != nullptr,然后call这个ptr,就调用到了类B的成员函数。

但是C++做这些有天然的不足,ptr不是nullptr倒是可以,但是可能类B早被delete了,但是存储在你类A的ptr依然不是nullptr,你call直接挂,当然用C11的shared_ptr、weak_ptr这种东西,倒是也是一个解决方案,不过总的来说,就是不爽,这得多麻烦?
也就是C++,我们还是难以跳出手动管理内存这个圈子。
C#的GC自动管理则完全通杀这些问题。

还有一个就是,C#中,委托是一个多播的实现,比如:
[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. static void Main(string[] args)  
  2. {  
  3.     //CallbackFunc StaticCbF = new CallbackFunc(StaticCallback);  
  4.     //StaticCbF(123); //Call to static void StaticCallback(int i)  
  5.   
  6.     //CNewTest c = new CNewTest();  
  7.     //CallbackFunc MembCbF = new CallbackFunc(c.MemberCallback);  
  8.     //MembCbF(1234); //Call to c->MemberCallback  
  9.   
  10.     CallbackFunc cbF = new CallbackFunc(StaticCallback);  
  11.     CNewTest c = new CNewTest();  
  12.     cbF += c.MemberCallback;  
  13.     cbF(123);  
  14.   
  15.     Console.ReadKey();  
  16. }  
输出:
123
123
this CNewTest.

简单的说,就是cbF这个玩意儿里面,有一个泛型的List。。。。在Call的时候,是跑表来一个个call的,十分方便。
说白了,委托就是一个【方法的接口】,只要方法对就行,安全又舒服。
于是委托+事件这种多播实现,就能很好的做出一个观察者模式。

那么多弊端出来了,C11委员会也发现了这些问题,就加入了一个std::function,这个东西呢,说简单了,它就是差不多用来实现委托的功能的(不是实现事件哦)。
还是回到上面那个C#写的ConsoleApplication1,我们用std::function重来一遍。
(编译环境为VS2013,Windows SDK。)

使用std::function前,需要包含#include <functional>。


我们准备一下函数:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. class CNewTest  
  2. {  
  3.     char m_str[128];  
  4.   
  5. public:  
  6.     void MemberCallback(int i)  
  7.     {  
  8.         strcpy(m_str,"this CNewTest.\n");  
  9.   
  10.         printf("%d\n",i);  
  11.         printf(m_str);  
  12.     }  
  13. };  
  14.   
  15. void StaticCallback(int i)  
  16. {  
  17.     printf("%d\n",i);  
  18. }  

一个直接的函数,一个类成员函数。
首先我们来调用StaticCallback:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int main()  
  2. {  
  3.     std::function<void (int)> fp = std::function<void (int)>(&StaticCallback);  
  4.     fp(123);  
  5.   
  6.     getchar();  
  7.     return 0;  
  8. }  
输出:123

这样写有点多啊,恩,我们是C++,有typedef。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int main()  
  2. {  
  3.     typedef std::function<void (int)> declare_fp;  
  4.   
  5.     declare_fp fp = declare_fp(&StaticCallback);  
  6.     fp(123);  
  7.   
  8.     getchar();  
  9.     return 0;  
  10. }  

typedef多了一行代码,而且不直观,不怕,我们有auto。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int main()  
  2. {  
  3.     auto fp = std::function<void (int)>(&StaticCallback);  
  4.     fp(123);  
  5.   
  6.     getchar();  
  7.     return 0;  
  8. }  
在实际使用中,视情况而定用auto还是typedef,二者配合是比较好的。
std::function还可以配合lamdba这种匿名函数来使用,有逆天级别的效果 ,这里不太深究,可能我日后会写lamdba的文章。)

是不是感觉,这tmd的太像C#的委托了?不过C#的委托是new的,我们C++的则可以直接开在stack上,当然我们new,也可以。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int main()  
  2. {  
  3.     typedef std::function<void (int)> declare_fp;  
  4.   
  5.     declare_fp* fp = new declare_fp(&StaticCallback);  
  6.     (*fp)(123);  
  7.   
  8.     delete fp; //手动删除  
  9.   
  10.     getchar();  
  11.     return 0;  
  12. }  

然后我们来测试调用成员函数指针:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int main()  
  2. {  
  3.     auto fp = std::function<void (int)>(&StaticCallback);  
  4.     fp(123);  
  5.   
  6.     CNewTest* c = new CNewTest();  
  7.     auto fp2 = std::bind(&CNewTest::MemberCallback,c,std::placeholders::_1);  
  8.     fp2(1234);  
  9.   
  10.     delete c;  
  11.   
  12.     getchar();  
  13.     return 0;  
  14. }  
输出:
123
1234
this CNewTest.

我们使用std::bind,生成了一个std::function,调用这个function即可调用过去成员函数了。
现在我们的CNewTest是在Heap上的,我们如果在stack上呢?估计你肯定想到了:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. CNewTest c;  
  2. auto fp2 = std::bind(&CNewTest::MemberCallback,&c,std::placeholders::_1);  
  3. fp2(1234);  

很多人可能会奇怪,后面这个std::placeholders::_1是什么意思?
其实就是表示bind上去的这个函数方法有1个参数,这个参数是个泛型的,如果我们不让其生成泛型参数,而是直接写一个绝对值,比如:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. CNewTest* c = new CNewTest();  
  2. auto fp2 = std::bind(&CNewTest::MemberCallback,c,3333);  
  3. fp2();  
则输出是:
123
3333
this CNewTest.

即便你这样调用:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. CNewTest* c = new CNewTest();  
  2. auto fp2 = std::bind(&CNewTest::MemberCallback,c,3333);  
  3. fp2(1234);  
输出依然是上面的,可以理解为一个强制默认参数的作用。

std::function在实际应用中,假设我们有一个函数,它有一个callback参数,当某些任务完成后,它call这个callback函数:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void DoWork(std::function<void (int)> callback)  
  2. {  
  3.     //DoWork....  
  4.     int result[] = {1,2,3};  
  5.     for (int i : result)  
  6.         callback(i);  
  7. }  

则可以这样调用:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int main()  
  2. {  
  3.     CNewTest c;  
  4.     auto fp2 = std::bind(&CNewTest::MemberCallback,&c,std::placeholders::_1);  
  5.     DoWork(fp2);  
  6.   
  7.     getchar();  
  8.     return 0;  
  9. }  

输出:
1
this CNewTest.
2
this CNewTest.
3
this CNewTest.

前面说了,std::function是可以new的,也就是,我们可以设计这样一个函数:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void DoWork2(std::shared_ptr<std::function<void (int)>> callback)  
  2. {  
  3.     //DoWork2....  
  4.     int result[] = {1,2,3};  
  5.     for (int i : result)  
  6.         (*callback.get())(i);  
  7. }  

然后这样调用:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int main()  
  2. {  
  3.     {  
  4.         CNewTest c;  
  5.         auto fp = std::make_shared<std::function<void (int)>>  
  6.             (std::function<void (int)>(std::bind(&CNewTest::MemberCallback,&c,std::placeholders::_1)));  
  7.         DoWork2(fp);  
  8.   
  9.         //delete fp.  
  10.     }  
  11.   
  12.     getchar();  
  13.     return 0;  
  14. }  

shared_ptr会保证我们new的std::function被安全的delete。
这个指针我们可以到处扔。。。。比C#的委托自由度大很多。

std::function本身没有多播的实现,你可以自己使用std::vector等东西包起来自己做一个。
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值