目录
function是⼀个函数对象的“容器”,在概念上它像是C++中函数指针类型的泛化,是⼀种“智能函 数指针”。它以对象的形式封装了原始的函数指针或函数对象,能够容纳任意符合函数签名的可调 ⽤对象。因此它可以被⽤于回调机制,暂时保管函数或函数对象,在之后需要的时机再调⽤这些 函数或函数对象,使回调机制拥有更多的弹性。
与原始的函数指针相⽐,function对象的体积要稍微⼤⼀点(3个指针的⼤⼩),速度要稍微慢⼀ 点(10%左右的性能差距),但这些缺点与它带给程序的巨⼤好处相⽐微不⾜道。
同bind⼀样,function也不是⼀个单独的类,⽽是⼀个⼤的类家族。function可以容纳0到10个参 数的函数,所以它有多个类,其命名分别是function0到function10。但我们通常不直接使⽤它 们,⽽是使⽤⼀个更通⽤的function类。
一、简单使用
function只需要⼀个模板参数,这个参数就是将要容纳的函数类型,尖括号中的类型声明就是⼀个没有函数名的函数原型。如果我们已经知道将要容纳的函数,那么也可以⽤关键字decltype来直接获取函数类型。
function的构造函数可以接收任意符合模板中声明的函数类型的可调⽤对象,如函数指针和函数对象,或者是另⼀个function对象的引⽤,之后在内部存储⼀份它的拷⻉。 ⽆参的构造函数或传⼊空指针构造将创建⼀个空的function对象,不持有任何可调⽤物,调⽤空的function对象将抛出 bad_function_call异常,因此在使⽤function前最好检测⼀下它的有效性。
empty( ):可以⽤来测试 function是否为空,或者⽤重载操作符operator!来测试function是否为空,function对象也可以 在⼀个bool语境中直接测试它是否为空。
clear( ):可以直接将function对象置空,它与直接赋值0具有同样的效果。
模板成员函数target( ):可以返回function对象内部持有的可调⽤物Functor的指针,如果 function为空则返回空指针nullptr。
contains( ):可以检测function是否持有⼀个Functor对象。
operator( ):它把传⼊的参数转交给内部保存的可调⽤物,完成真正的函数调⽤。
function还重载了⽐较操作符operator==和operator!=,可以与被包装的函数或函数对象进⾏⽐较。如果function存储的是函数对象,那么要求函数对象必须重载operator==,它才是可⽐较的。 两个function对象不能使⽤==和!=直接进⾏⽐较。因为function存在bool的隐式转换,function 定义了两个function对象的operator==,但没有实现,企图⽐较两个function对象会导致编译错误。
1.存储普通函数
#include <boost/function.hpp>
//普通函数
int sum(int a, float b)
{
float sum = a + b;
std::cout << "[自由函数sum]两个参数和:" << a << "+" << b << "=" << sum << std::endl;
return sum;
}
void StudyRefClass::TestFunction()
{
boost::function<int()> fun1; //容纳一个返回类型为int、无参函数的function对象
boost::function<int(int, std::string)> fun2; //容纳一个返回类型为int、有两个参数的function对象
if (fun2.empty())
{
std::cout << "fun2是一个空的function对象" << std::endl;
}
boost::function<decltype(sum)> fun3(sum); // boost::function<int(int a,int b)> fun3
if (fun3.empty())
{
std::cout << "fun3是一个空的function对象" << std::endl;
}
else {
std::cout << "fun3参数个数:" << fun3.arity << std::endl;
if (fun3 == sum)
{
std::cout << "fun3与sum相等." << std::endl;
}
fun3(10, 20); //[自由函数sum]两个参数和:10+20=30
fun3.clear(); //fun3=0;
}
}
2.存储成员函数
struct demo {
int add(int a, int b) {
return a + b;
}
int operator()(int x) const {
return x * x;
}
};
demo _demo;
boost::function<int(demo&, int, int)> fun(&demo::add);
std::cout << fun(_demo, 10, 11) << std::endl; //21
二、与bind/lambda结合使用
function可以配合bind/lambda使⽤,以存储bind/lambda表达式的结果,使bind/lambda能够被多次调⽤。
1.与bind结合使用
(1)存储普通函数
boost::function<decltype(sum)> fun4;
fun4 = boost::bind(sum, _1, _2);
fun4(2, 3); //[自由函数sum]两个参数和:2+3=5
(2) 存储成员函数
a. 在function中指定类的类型,结合bind绑定成员函数。
boost::function<int(demo&, int, int)> fun_demo_add1;
fun_demo_add1 = boost::bind(&demo::add, _1, _2, _3); //bind绑定成员函数
std::cout << fun_demo_add1(_demo, 10, 11) << std::endl; //21
b. 在function中写出成员函数签名,结合bind绑定类实例
boost::function<int(int, int)> fun_demo_add2;
demo _demo;
fun_demo_add2 = boost::bind(&demo::add, &_demo, _1, _2); //bind绑定类实例
std::cout << fun_demo_add2(11, 12) << std::endl; //23
举个栗子1:function存储函数对象
boost::function<int(demo&, int)> fun_demo_operator1;
fun_demo_operator1 = boost::bind(&demo::operator(), _1, _2); //bind绑定成员函数对象
std::cout << fun_demo_operator1(_demo, 8) << std::endl; //64
boost::function<int(int)> fun_demo_operator;
demo _demo;
fun_demo_operator = boost::bind(&demo::operator(), &_demo, _1); //bind绑定类实例
std::cout << fun_demo_operator(10) << std::endl; //100
举个栗子2:function存储成员函数print()
struct point {
int x, y;
point(int a = 0, int b = 0) :x(a),y(b)
{
}
void print()
{
std::cout << x << "," << y << std::endl;
}
};
point _point;
boost::function<void(point&)> fun_point_print;
fun_point_print = boost::bind(&point::print, _1);
fun_point_print(_point); //0,0
point _point1;
boost::function<void()> fun_point_print1;
fun_point_print1 = boost::bind(&point::print, _point1);
fun_point_print1(); //0,0
2.与lambda结合使用
boost::function<int(int, int)> fun = [](int a, int b) {
return a * b;
};
std::cout << fun(10, 11) << std::endl; //110
三、结合ref库使用
function使⽤拷⻉语义保存参数,当参数很⼤时,拷⻉的代价往往很⾼,甚⾄有时候不能拷⻉参数。这时我们可以向ref库求助,它允许以引⽤的⽅式传递参数,能够降低function拷⻉的代价。 function并不要求ref库提供operator(),因为它能够⾃动识别包装类reference_wrapper,并调⽤get()⽅法获得被包装的对象。
function能够直接调⽤被ref库包装的函数对象,这个功能可以弥补boost.ref没有operator()的遗憾。
1.不使用ref库存储函数对象
demo _demo;
boost::function<int(int)> fun(_demo);
std::cout << "不用ref:" << fun(6) << std::endl; //不用ref:36
2.使用ref库存储函数对象
demo _demo;
boost::function<int(int)> fun_5(boost::cref(_demo));
std::cout << "用ref:" << fun_5(6) << std::endl;
四、function用于回调
function可以容纳任意符合函数签名式的可调⽤物,因此它⾮常适合代替函数指针,存储⽤于回调的函数,⽽且它的强⼤功能会使代码更灵活、更富有弹性。
回调函数可以是普通函数、类静态成员函数、类成员函数(结合bind使用)、函数对象。
struct CallBackTestClass {
private:
boost::function<void(int)> fun;//使⽤function代替函数指针作为内部类型保存回调函数,存储形式为void(int)
int n;
public:
CallBackTestClass(int i) :n(i) {
}
void accept(boost::function<void(int)> f) //接收回调函数
{
fun = f;
}
void run() //调用回调函数
{
fun(n);
}
};
1.回调函数是普通函数
void TestCallBack1(int i)
{
std::cout << "立方:" << i * i * i << std::endl;
}
CallBackTestClass test(5);
test.accept(TestCallBack1);
test.run();
2.回调函数是静态成员函数
static void TestCallBack(int i);//头文件函数声明
void StudyRefClass::TestCallBack(int i)//cpp文件函数实现
{
std::cout << "平方:" << i * i << std::endl;
}
CallBackTestClass test(5);
test.accept(TestCallBack);
test.run();
3.回调函数是普通成员函数
void TestCallBackFun(int i);//函数声明
void StudyRefClass::TestCallBackFun(int i)//函数实现
{
std::cout << "自增一:" << ++i << std::endl;
}
CallBackTestClass test(5);
test.accept(boost::bind(&StudyRefClass::TestCallBackFun,this,_1));
test.run();
当修改accept函数使⽤模板函数时(这种形式更加灵活,⽤户可以在不知道也不关⼼内部存储形式的情况下传递任何可调⽤对象,包括函数指针和函数对象),用户无须改变回调的接⼝就可以解耦客户代码,使客户代码不必绑死在⼀种回调形式上。
template <typename callback>
void accept(callback f)
{
fun = f;
}
void StudyRefClass::TestCallBackFun(int i) //普通成员函数
{
std::cout << "自增一:" << ++i << std::endl;
}
void StudyRefClass::TestCallBackFun(int a, int b) //普通成员函数
{
std::cout << a << "," << b << std::endl;
}
CallBackTestClass test(5);
test.accept(boost::bind(&StudyRefClass::TestCallBackFun,this,_1));
test.run(); //自增一:6
test.accept(boost::bind(&StudyRefClass::TestCallBackFun, this, 10,20));
test.run(); //10,20
4.回调函数是函数对象
class CallBackObject {
private:
int x;
public:
CallBackObject(int _x) : x(_x) {
}
void operator()(int i) {
std::cout <<"x:"<< ++x << ",i:" << i<<" ";
std::cout << x * i << std::endl;
}
};
CallBackTestClass test(5);
CallBackObject obj(2);
test.accept(obj); // 回调函数是函数对象
test.run(); //x:3,i:5 15
test.run(); //x:4,i:5 20
五、与auto对比
有些时候,关键字auto可以取代function。 但auto和function的实现有很⼤的不同。
function类似⼀个容器,可以容纳任意有operator()的 类型(函数指针、函数对象、lambda表达式),它是运⾏时的,可以任意拷⻉、赋值、存储其他可调⽤物。⽽auto仅是在编译期推导出的⼀个静态类型变量,很难再赋予它其他值,它也⽆法容纳其他的类型,不能⽤于泛型编程。
当需要存储⼀个可调⽤物⽤于回调的时候,最好使⽤function,它具有更强的灵活性,特别是把回调作为类的⼀个成员的时候我们只能使⽤function。
auto也有它的优点,它的类型是在编译期推导的,运⾏时没有“开销”,它在效率上要⽐function略⾼⼀点。但auto声明的变量不能存储其他类型的可调⽤物,灵活性较差,只能⽤于有限范围的延后回调。
六、与C++标准库对比
std::function与boost::function基本相同,它们只有少量的区别:
- 没有clear()和empty()成员函数。
- 提供assign()成员函数。
- explicit显式bool转型。
所以,同shared_ptr⼀样,std::function在函数返回值或函数参数等语境⾥转型bool需要使⽤ static_cast<bool>(f)或!f的形式。