【Boost】boost库中function的用法_boost::function0<void>-程序员宅基地

要开始使用 Boost.Function, 就要包含头文件 "boost/function.hpp", 或者某个带数字的版本,从"boost/function/function0.hpp" 到 "boost/function/function10.hpp". 如果你知道你想保存在 function 中的函数的参数数量,这样做可以让编译器仅包含需要的头文件。如果包含 "boost/function.hpp", 那么就会把其它的头文件也包含进去。

理解被存函数的最佳方法是把它想象为一个普通的函数对象,该函数对象用于封装另一个函数(或函数对象)。这个被存的函数的最大用途是它可以被多次调用,而无须在创建 function 时立即使用。在声明 functions 时,声明中最重要的部分是函数的签名。这部分即是告诉 function 它将保存的函数或函数对象的签名和返回类型。我们已经看到,有两种方法来执行这个声明。这里有一个完整的程序,程序声明了一个 boost::function ,它可以保存返回bool (或某个可以隐式转换为 bool 的类型)并接受两个参数的类函数实体,第一个参数可以转换为 int, 第二个参数可以转换为 double.

  
  
   
  1. #include <iostream>  
  2. #include "boost/function.hpp"  
  3. bool some_func(int i,double d) {  
  4.   return i>d;  
  5. }  
  6. int main() {  
  7.   boost::function<bool (int,double)> f;  
  8.   f=&some_func;  
  9.   f(10,1.1);  
  10. }  

当 function f 首次创建时,它不保存任何函数。它是空的,可以在一个布尔上下文中进行测试。如果你试图调用一个没有保存任何函数或函数对象的 function ,它将抛出一个类型 bad_function_call 的异常。为了避免这个问题,我们用普通的赋值语法把一个指向 some_func 的指针赋值给 f 。这导致 f 保存了到 some_func 的指针。最后,我们用参数10 (一个 int) 和 1.1 (一个 double)来调用 f (用函数调用操作符)。要调用一个 function, 你必须提供被存函数或函数对象所期望的准确数量的参数。

回调的基础

我们先来看看在没有 Boost.Function 以前我们如何实现一个简单的回调,然后再把代码改为使用 function, 并看看会带来什么优势。我们从一个支持某种简单的回调形式的类开始,它可以向任何对新值关注的对象报告值的改变。这里的回调是一种传统的C风格回调,即使用普通函数。这种回调用可用于象GUI控制这样的场合,它可以通知观察者用户改变了它的值,而不需要对监听该信息的客户有任何特殊的知识。

 
 
  
  1. #include <iostream>  
  2. #include <vector>  
  3. #include <algorithm>  
  4. #include "boost/function.hpp"  
  5. void print_new_value(int i) {  
  6.   std::cout <<   
  7.     "The value has been updated and is now " << i << '/n';  
  8. }  
  9. void interested_in_the_change(int i) {  
  10.   std::cout << "Ah, the value has changed./n";  
  11. }  
  12. class notifier {  
  13.   typedef void (*function_type)(int);  
  14.   std::vector<function_type> vec_;  
  15.   int value_;  
  16. public:  
  17.   void add_observer(function_type t) {  
  18.     vec_.push_back(t);  
  19.   }  
  20.   void change_value(int i) {  
  21.     value_=i;  
  22.     for (std::size_t i=0;i<vec_.size();++i) {  
  23.       (*vec_[i])(value_);  
  24.     }  
  25.   }  
  26. };  
  27. int main() {  
  28.   notifier n;  
  29.   n.add_observer(&print_new_value);  
  30.   n.add_observer(&interested_in_the_change);  
  31.   
  32.   n.change_value(42);  
  33. }  

这里的两个函数,print_new_value 和 interested_in_the_change, 它们的函数签名都兼容于 notifier 类的要求。这些函数指针被保存在一个 vector 内,并且无论何时它的值被改变,这些函数都会在一个循环里被调用。调用这些函数的一种语法是:

(*vec_[i])(value_);

值(value_)被传递给解引用的函数指针(即 vec_[i] 所返回的)。另一种写法也是有效的,即这样:

vec_[i](value_);

这种写法看起来更好看些,但更为重要的是,它还可以允许你把函数指针更换为 Boost.Function 而没有改变调用的语法。现在,工作还是正常的,但是,唉,函数对象不能用于这个 notifier 类。事实上,除了函数指针以外,别的任何东西都不能用,这的确是一种局限。但是,如果我们使用 Boost.Function,它就可以工作。重写这个 notifier类非常容易。

 
 
  
  1. class notifier {  
  2.   typedef boost::function<void(int)> function_type;  
  3.   std::vector<function_type> vec_;  
  4.   int value_;  
  5. public:  
  6.   template <typename T> void add_observer(T t) {  
  7.     vec_.push_back(function_type(t));  
  8.   }  
  9.   
  10.   void change_value(int i) {  
  11.     value_=i;  
  12.     for (std::size_t i=0;i<vec_.size();++i) {  
  13.       vec_[i](value_);  
  14.     }  
  15.   }  
  16. };  

首先要做的事是,把 typedef 改为代表 boost::function 而不是函数指针。之前,我们定义的是一个函数指针;现在,我们使用泛型方法,很快就会看到它的用途。接着,我们把成员函数 add_observer 的签名改为泛化的参数类型。我们也可以把它改为接受一个 boost::function,但那样会要求该类的用户必须也知道 function 的使用方法[2],而不是仅仅知道这个观察者类型的要求就行了。应该注意到 add_observer 的这种变化并不应该是转向function 的结果;无论如何代码应该可以继续工作。我们把它改为泛型的;现在,不管是函数指针、函数对象,还是 boost::function 实例都可以被传递给 add_observer, 而无须对已有用户代码进行任何改动。把元素加入到vector 的代码有一些修改,现在需要创建一个 boost::function<void(int)> 实例。最后,我们把调用这些函数的语法改为可以使用函数、函数对象以及 boost::function 实例[3]。这种对不同类型的类似函数的"东西"的扩展支持可以立即用于带状态的函数对象,它们可以实现一些用函数很难做到的事情。

[2] 他们应该知道 Boost.Function,但如果他们不知道呢?我们添加到接口上的任何东西都必须及时向用户解释清楚。

[3] 现在我们知道,一开始我们就应该用这种语法。

 
 
  
  1. class knows_the_previous_value {  
  2.   int last_value_;  
  3. public:  
  4.   void operator()(int i) {  
  5.     static bool first_time=true;  
  6.     if (first_time) {  
  7.       last_value_=i;  
  8.       std::cout <<   
  9.         "This is the first change of value, /  
  10. so I don't know the previous one./n";  
  11.       first_time=false;  
  12.       return;  
  13.     }  
  14.     std::cout << "Previous value was " << last_value_ << '/n';  
  15.     last_value_=i;  
  16.   }  
  17. };  

这个函数对象保存以前的值,并在值被改变时把旧值输出到 std::cout 。注意,当它第一次被调用时,它并不知道旧值。这个函数对象在函数中使用一个静态 bool 变量来检查这一点,该变量被初始化为 true. 由于函数中的静态变量是在函数第一次被调用时进行初始化的,所以它仅在第一次调用时被设为 true 。虽然也可以在普通函数中使用静态变量来提供状态,但是我们必须知道那样不太好,而且很难做到多线程安全。因此,带状态的函数对象总是优于带静态变量的普通函数。notifier 类并不关心这是不是函数对象,只要符合要求就可以接受。以下更新的例子示范了它如何使用。

 
 
  
  1. int main() {  
  2.   notifier n;  
  3.   n.add_observer(&print_new_value);  
  4.   n.add_observer(&interested_in_the_change);  
  5.   n.add_observer(knows_the_previous_value());  
  6.   
  7.   n.change_value(42);  
  8.   std::cout << '/n';  
  9.   n.change_value(30);  
  10. }  

关键一点要注意的是,我们新增的一个观察者不是函数指针,而是一个 knows_the_previous_value 函数对象的实例。运行这段程序的输出如下:

The value has been updated and is now 42
Ah, the value has changed.
This is the first change of value, so I don't know the previous one.

The value has been updated and is now 30
Ah, the value has changed.
Previous value was 42

在这里最大的优点不是放宽了对函数的要求(或者说,增加了对函数对象的支持),而是我们可以使用带状态的对象,这是非常需要的。我们对 notifier 类所做的修改非常简单,而且用户代码不受影响。如上所示,把 Boost.Function 引入一个已有的设计中是非常容易的。

类成员函数

Boost.Function 不支持参数绑定,这在每次调用一个 function 就要调用同一个类实例的成员函数时是需要的。幸运的是,如果这个类实例被传递给 function 的话,我们就可以直接调用它的成员函数。这个 function 的签名必须包含类的类型以及成员函数的签名。换言之,显式传入的类实例要作为隐式的第一个参数,this。这样就得到了一个在给出的对象上调用成员函数的函数对象。看一下以下这个类:

 
 
  
  1. class some_class {  
  2. public:  
  3.   void do_stuff(int i) const {  
  4.     std::cout << "OK. Stuff is done. " << i << '/n';  
  5.   }  
  6. };  

成员函数 do_stuff 要从一个 boost::function 实例里被调用。要做到这一点,我们需要 function 接受一个some_class 实例,签名的其它部分为一个 void 返回以及一个 int 参数。对于如何把 some_class 实例传给 function,我们有三种选择:传值,传引用,或者传址。如何要传值,代码就应该这样写[4]

[4] 很少会有理由来以传值的方式传递对象参数。

 
 
  
  1. boost::function<void(some_class,int)> f;  

注意,返回类型仍旧在最开始,后跟成员函数所在的类,最后是成员函数的参数类型。它就象传递一个 this 给一个函数,该函数暗地里用类实例调用一个非成员函数。要把函数 f 配置为成员函数 do_stuff, 然后调用它,我们这样写:

 
 
  
  1. f=&some_class::do_stuff;  
  2. f(some_class(),2);  

如果要传引用,我们要改一下函数的签名,并传递一个 some_class 实例。

 
 
  
  1. boost::function<void(some_class&,int)> f;  
  2. f=&some_class::do_stuff;  
  3. some_class s;  
  4. f(s,1);  

最后,如果要传 some_class 的指针[5],我们就要这样写:

[5] 裸指针或智能指针皆可。

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. boost::function<void(some_class*,int)> f;  
  2. f=&some_class::do_stuff;  
  3. some_class s;  
  4. f(&s,3);  

好了,所有这些传递"虚拟 this"实例的方法都已经在库中提供。当然,这种技术也是有限制的:你必须显式地传递类实例;而理想上,你更愿意这个实例被绑定在函数中。乍一看,这似乎是 Boost.Function 的缺点,但有别的库可以支持参数的绑定,如 Boost.Bind 和 Boost.Lambda. 我们将在本章稍后的地方示范这些库会给 Boost.Function 带有什么好处。

带状态的函数对象

我们已经看到,由于支持了函数对象,就可以给回调函数增加状态。考虑这样一个类,keeping_state, 它是一个带状态的函数对象。keeping_state 的实例记录一个总和,它在每次调用操作符执行时被增加。现在,将该类的一个实例用于两个 boost::function 实例,结果有些出人意外。

 
 
  
  1. #include <iostream>  
  2. #include "boost/function.hpp"  
  3. class keeping_state {  
  4.   int total_;  
  5. public:  
  6.   keeping_state():total_(0) {}  
  7.   int operator()(int i) {  
  8.     total_+=i;  
  9.     return total_;  
  10.   }  
  11.   int total() const {  
  12.     return total_;  
  13.   }  
  14. };  
  15. int main() {  
  16.   keeping_state ks;  
  17.   boost::function<int(int)> f1;  
  18.   f1=ks;  
  19.   boost::function<int(int)> f2;  
  20.   f2=ks;  
  21.   std::cout << "The current total is " << f1(10) << '/n';  
  22.   std::cout << "The current total is " << f2(10) << '/n';  
  23.   std::cout << "After adding 10 two times, the total is "   
  24.     << ks.total() << '/n';  
  25. }  

写完这段代码并接着执行它,程序员可能期望保存在 ks 的总和是20,但不是;事实上,总和为0。以下是这段程序的运行结果。

The current total is 10
The current total is 10
After adding 10 two times, the total is 0

原因是每一个 function 实例(f1 和 f2)都含有一个 ks 的拷贝,这两个实例得到的总和都是10,但 ks 没有变化。这可能是也可能不是你想要的,但是记住,boost::function 的缺省行为是复制它要调用的函数对象,这一点很重要。如果这导致不正确的语义,或者如果某些函数对象的复制代价太高,你就必须把函数对象包装在boost::reference_wrapper 中,那样 boost::function 的复制就会是一个 boost::reference_wrapper 的拷贝,它恰好持有一个到原始函数对象的引用。你无须直接使用 boost::reference_wrapper ,你可以使用另两个助手函数,ref 和 cref。 这两函数返回一个持有到某特定类型的引用或 const 引用的 reference_wrapper。在前例中,要获得我们想要的语义,即使用同一个 keeping_state 实例,我们就需要把代码修改如下:

 
 
  
  1. int main() {  
  2.   keeping_state ks;  
  3.   boost::function<int(int)> f1;  
  4.   f1=boost::ref(ks);  
  5.   
  6.   boost::function<int(int)> f2;  
  7.   f2=boost::ref(ks);  
  8.   
  9.   std::cout << "The current total is " << f1(10) << '/n';  
  10.   std::cout << "The current total is " << f2(10) << '/n';  
  11.   std::cout << "After adding 10 two times, the total is "   
  12.     << ks.total() << '/n';  
  13. }  

boost::ref 的用途是通知 boost::function,我们想保存一个到函数对象的引用,而不是一个拷贝。运行这个程序有以下输出:

The current total is 10
The current total is 20
After adding 10 two times, the total is 20

这正是我们想要的结果。使用 boost::ref 和 boost::cref 的不同之处就象引用与 const 引用的差异,对于后者,你只能调用其中的常量成员函数。以下例子使用一个名为 something_else 的函数对象,它有一个 const 的调用操作符。

 
 
  
  1. class something_else {  
  2. public:  
  3.   void operator()() const {  
  4.     std::cout << "This works with boost::cref/n";  
  5.   }  
  6. };  

对于这个函数对象,我们可以使用 boost::ref 或 boost::cref.

 
 
  
  1. something_else s;  
  2. boost::function0<void> f1;  
  3. f1=boost::ref(s);  
  4. f1();  
  5. boost::function0<void> f2;  
  6. f2=boost::cref(s);  
  7. f2();  

如果我们改变了 something_else 的实现,使其函数为非const, 则只有 boost::ref 可以使用,而 boost::cref 将导致一个编译期错误。

 
 
  
  1. class something_else {  
  2. public:  
  3.   void operator()() {  
  4.     std::cout <<   
  5.       "This works only with boost::ref, or copies/n";  
  6.   }  
  7. };  
  8.   
  9. something_else s;  
  10. boost::function0<void> f1;  
  11. f1=boost::ref(s); // This still works  
  12. f1();   
  13. boost::function0<void> f2;  
  14. f2=boost::cref(s); // This doesn't work;   
  15.                    // the function call operator is not const  
  16. f2();  

如果一个 function 包含一个被 boost::reference_wrapper 所包装的函数对象,那么复制构造函数与赋值操作就会复制该引用,即 function 的拷贝将引向原先的函数对象。

 
 
  
  1. int main() {  
  2.   keeping_state ks;  
  3.   boost::function1<int,int> f1;  // 译注:原文为boost::function<int,int> f1,有误  
  4.   f1=boost::ref(ks);  
  5.   
  6.   boost::function1<int,int> f2(f1);  // 译注:原文为boost::function<int,int> f2(f1),有误   
  7.   boost::function1<short,short> f3;  // 译注:原文为boost::function<short,short> f3,有误   
  8.   f3=f1;  
  9.   
  10.   std::cout << "The current total is " << f1(10) << '/n';  
  11.   std::cout << "The current total is " << f2(10) << '/n';  
  12.   std::cout << "The current total is " << f3(10) << '/n';  
  13.   std::cout << "After adding 10 three times, the total is "   
  14.     << ks.total() << '/n';  
  15. }  

这等同于使用 boost::ref 并把函数对象 ks 赋给每一个 function 实例。

给回调函数增加状态,可以发挥巨大的能力,这也正是使用 Boost.Function 与使用函数对象相比具有的非常突出的优点。


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/liujiayu2/article/details/50600825

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法