引入
// STL
<functional>
// boost
#include <boost/function.hpp>
using namespace boost;
是什么
- function是一个函数对象的“容器”,只要符合它声明中的函数类型,任何普通函数、成员函数、函数对象都可以存储在function对象中,然后在任何时候被调用
- 如果function存储的是函数对象,那么要求函数对象必须实现operator==,是可以比较的
- function可以配合bind/lambda使用,存储bind/lambda表达式的结果,使得bind/lambda能够被多次调用
- 因此它可以被用于回调机制,暂时保管函数或者函数对象,在之后需要的时机调用,使得回调机制拥有更多的弹性
- 与原始的函数指针相比,function对象的体积要大一点,速度要慢一点,但是它带来的好处更大。
类摘要
boost
boost::function也不是一个单独的类,而是一个大的类家族。它可以容纳0到10个参数的函数,所以也就有多个类,命名分别是function0到function10,但是我们通常不直接使用它们,而是用一个更通用的function类,它的类摘要如下:
stl
std::function的类摘要如下:
std::function与boost::function基本相同,区别在于:
- 没有clear()和empty()成员函数
- 没有assign()成员函数
- explicit显示bool转型
所以,同shared_ptr一样,在函数返回值或者函数参数等语境里转型bool需要使用static_cast< bool >(f)或者!!f的形式
声明形式
- function只需要一个模板参数,这个参数就是将要容纳的函数类型。比如:
// 将声明一个可以容纳返回值为int,无参函数的function对象
function<int()> func;
// 将声明一个可以容纳返回值为int,两个int类型参数的函数的function对象
function<int (int a, int b)> func2;
function<int (int, int)> func3;
- 如果我们已经知道了将要容纳的函数,那么也可以用关键字decltype来直接获取函数类型。比如:
int f(int a, int b) {....};
function<decltype(f)> func;
操作函数
function的构造函数:
- 它可以接受任意符合模板中声明的函数类型的可调用对象,比如函数指针和函数对象,或者是另一个function对象的引用,之后在内部存储一份它的拷贝
- 无参构造函数或者传入空指针构造将创建一个空function对象,不持有任何可调用物,调用空的function对象将抛出bad_function_call异常,因此在使用function之前最好检测一下它的有效:
- 可以用empty()测试function是否为空
- 或者用重载操作符operator!来测试
- 也可以在一个bool语境中直接测试它是否为空
#include <iostream>
#include <functional>
int main()
{
std::function<int()> f = nullptr;
try {
f();
} catch(const std::bad_function_call& e) {
std::cout << e.what() << '\n';
}
}
function的其他成员函数功能如下:
- clear()可以直接将function对象置空,相当于直接赋值0
- 模板成员函数target()可以返回function对象内部持有的可调用物Functor的指针,如果function为空则返回空指针nullptr
- contains()可以检测function是否持有一个Functor对象
- function提供了operator(),它把传入的参数转交给内部保存的可调用物,完成真正的函数调用
function还重载了比较操作符operator==和operator!=,可以与被包装的函数或者函数对象进行比较。如果function存储的是函数指针,那么比较相当于:function.target< Functor>() == func_pointer
比如:
function<int (int, int) > func(f);
assert(func == f);
两个function对象不能使用==或者!=直接比较,这是特意的。因为function存在bool的隐式转换,function定义了两个function对象的operator==
但没有实现,企图比较两个function对象会导致编译错误
使用
基本用法
#include <iostream>
#include <functional>
void go()
{
printf("%s", "go");
}
int main()
{
//std::function<返回值(参数)>函数指针 = 指向函数
std::function <void(void)>fun1 = go;
fun1();
}
#include <iostream>
#include <functional>
using namespace std;
void print_num(int i)
{
printf("%d\n", i);
}
int main() {
// 存储自由函数
std::function<void(int)> f_display = print_num;
f_display(-9);
// 存储 lambda
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42();
// 存储到 std::bind 调用的结果
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337();
return 0;
}
#include <iostream>
#include <functional>
int main()
{
std::function <void(void)>fun2 = []() { printf("%s", "golamba" ); };
fun2();
}
#include <iostream>
#include <functional>
int add(int a, int b)
{
return a + b;
}
using namespace std;
int main()
{
function <int(int, int)>fun3 = add; //20
cout << fun3(10, 10) << endl;
function <int(int, int)>fun4 = [](int a, int b)->int {return a + b; };
cout << fun4(10, 10) << endl;
}
函数对象
#include <iostream>
#include <functional>
using namespace std;
struct PrintNum{
void operator()(int i) const {
printf("%d\n", i);
}
};
int main() {
// 存储到函数对象的调用
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18);
return 0;
}
成员函数
假如我们如下一个类,它既有普通成员函数,又重载了operator()
怎么存储成员函数呢?
第一种方法:
- 先用function中指定类的类型
- 然后用bind绑定成员函数
第二种方法:
- 先用function中指定成员函数的签名
- 然后在bind时直接绑定类的实例
#include <iostream>
#include <functional>
//使用成员函数()
class C {
public:
void memfunc(int x, int y) const {printf("%d", x + y);};
};
int main()
{
std::function<void(const C&, int, int)> mf;
mf = &C::memfunc;
mf(C(), 42, 77);
}
#include <iostream>
#include <functional>
using namespace std;
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { printf("%d\n", i + num_); }
int num_;
};
int main() {
// 存储到成员函数的调用
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1);
f_add_display(314159, 1);
// 存储到数据成员访问器的调用
std::function<int(Foo const&)> f_num = &Foo::num_;
std::cout << "num_: " << f_num(foo) << '\n';
// 存储到成员函数及对象的调用
using std::placeholders::_1;
std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
f_add_display2(2);
// 存储到成员函数和对象指针的调用
std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
f_add_display3(3);
return 0;
}
批量调用函数
#include <iostream>
#include <functional>
#include <vector>
using namespace std;
void func(int x, int y){
printf("%d ", x + y);
};
int main()
{
//initialize collections of tasks
std::vector<std::function<void(int, int)>> tasks;
tasks.push_back(func);
tasks.push_back([](int x, int y) {
printf("%d, %d", x, y);
});
//call each task
for(std::function<void(int, int)> f : tasks) {
f(33, 66);
}
}
使用ref库
function使用拷贝语义保存参数,当参数很大时或者参数不可以拷贝时怎么办呢?用ref库,它允许以引用的方式传递参数,能够降低function拷贝的代价。
- function并不要求ref库提供operator(),因为它能够自动识别包装类reference_wrapper,并调用get()方法获得被包装的对象
- 如果ref库不提供operator(),那么它将可能无法用于标准库算法,因为很多标准库算法总使用拷贝语义,算法内部的改变不能影响原对象。怎么解决?
用于回调
function可以代替函数指针,存储用于回调的函数.
为什么要替代呢?因为它非常灵活,无需改变回调的接口就可以解耦客户代码,使得客户代码不必绑死在一种回调形式上
class demo_class{
private:
typedef function<void (int)> func_t; // func_t代替回调函数
func_t func_; // function对象
int n_; //内部成员变量
public:
demo_class(int i) : n_(i){};
// demo_class使用模板函数accept()接受回调函数。之所以使用模板函数,是
// 因为它更灵活,用户可以在不必关心内部存储形式情况下传递任何可调用对象
template <typename CallBack>
void accept(CallBack f){ // 存储回调函数
func_ = f;
}
// run()用于调用回调函数
void run(){
func_(n);
}
};
//用于回调的普通函数
void call_back_func(int i){
cout << "gerarator call_back_func :" << i * 2 << "\n";
}
// 用于回调的类
class call_back_obj{
private:
int x_; //内部状态
public:
call_back_obj(int i) : x_(i) {};
void operator()(int i){
cout << "class call_back_func :" << i * 2 << "\n";
}
};
// 回调函数工厂类
class call_back_factory{
public:
void call_back_obj1(int i){
cout << "call_back_factory1 :" << i * 2 << "\n";
}
void call_back_obj2(int i, int j){
cout << "call_back_factory1 :" << i + j << "\n";
}
};
// function既可以接受函数指针也可以接受函数对象
int test_function_call(){
// 回调普通函数
demo_class dc(10);
dc.accept(test_function_call); //接受
dc.run(); //调用
//回调类
demo_class dc1(10);
call_back_obj cbo(2);
dc1.accept(ref(cbo));
dc1.run();
// 搭配bind
demo_class dcc(10);
call_back_factory cbf;
dcc.accept(bind(&call_back_factory::call_back_obj1, cbf, _1));
dcc.run();
dcc.accept(bind(&call_back_factory::call_back_obj2, cbf, _1, 5));
dcc.run();
}
对比auto
有时候关键字auto可以近似的取代function。比如:
但是它们的实现由很大的不同:
- function类似一个容器,可以容纳任意有operator()的类型(函数指针、函数对象、lambda表达式),它是运行时的,可以任意拷贝、赋值、存储其他可调用物
- 而auto仅是在编译期推导出的一个静态类型变量,很难再赋予其他值,也无法容纳其他的类型,不能用于泛型编程
那应该怎么选择呢?
- 当存储一个可调用物用于回调的时候,最好使用function,它具有更多的灵活性,特别是把回调作为类的一个成员的时候我们只能使用function
- auto也有它的优点,它的类型是在编译器推导的,没有运行时的开销,效率上要比function要高一点。但是它声明的变量不能存储其他类型的可调用物,不具有灵活性,只能用于有限范围的延后回调。
总结
- 类std::function<>,声明于
<function>
,提供多态包装器,允许你把可调用对象(比如function、member function、function object、lambda等)当作最高级对象(也叫做函数包装器) - 函数包装器目的是为了把函数包装起来,原理是函数指针
- 使用方法:std::function <返回值(函数参数)>函数指针 = 指向函数