本文从其 实现
和 使用
, 以及和 auto, bind等关系, 上单独说一说 function .
如果说 bind 是适配器(现有的可调用对象不适用时), ref是引用封装, 那么 function 就相应与对于各种各种可调用对象(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的包装或者容器. 实际上深究一下, 它应该说成 “智能函数指针” .
从 boost 库 ‘boost/function.hpp’ 中, 引入标准库, function 完成了可调用对象的统一约束.
引子
既然是可调用对象, 那么指定参数类型和返回值是肯定的, 例如:
1 |
|
实际上, 有具体的两条规则:
- 转换后的std::function对象的参数能转换为可调用实体的参数
- 可调用实体的返回值能转换为std::function对象的返回值
正文
function声明
对于一个函数, 在声明时, 是一定要指定返回值类型和参数的(function类模板内部有 typedef 定义返回值和参数类型).
可能有的编译器可以递归推倒, 例如:1
2
3
4
5
6
7
int func(double) { return 0; }
int main() {
std::function f{func}; // guide #1 deduces function<int(double)>
int i = 5;
std::function g = [&](double) { return i; }; // guide #2 deduces function<int(double)>
}
不过我还是劝你声明清楚, 或者使用 decltype
也行啊, 例如:1
2
3
4
5
int func(double) { return 0; }
std::function<int(double)> f = func;
std::function<std::decltype(func)> f = func;
不要写成 std::decltype(&func) , 这样推倒出来的是函数指针类型(而非函数类型).
类摘要
function是一些列重载类模板(参数不同), 但是一般使用一个更加通用的类function.1
2emplate< class R, class... Args >
class function<R(Args...)>; //R means return_type
类实现, 也就是封装了可调用函数对象签名的封装体(本质上还是一个functor), 具体略.
相关 成员函数
的作用:
- empty(): 检测 function 的有效性(是否有初始化) —boost only
- operator!() : 作用和empty()一样, 检验function是否为空(用法就是直接用function对象做谓词判断) —boost only
- clear() : 效果和 operator=()赋值为0一样, 置空函数对象 —boost only
- target(): 返回内部持有的可调用functor的指针(注意检查一下是否为nullptr)
- contains(): 检查function是否持有funtor对象 —boost only
- operator(): 相当于调用内部函数对象
也就是说, std中, 你判断谓词, 一般要使用 static_cast<bool>(function对象)
(std::function相对于, boost::function 阉割了太多功能)
最好不要比较两个function, 因为内部的语义不是太清楚, 以及function本身有bool谓词判断语义.
上面函数的使用案例:
1 |
|
function作用
统一调用形式的作用?
我们知道, 函数指针可以用于回调, 函数对象可以用于回调, lambda表示也可以, 甚至是bind适配后的成员函数也可以, 那么在不确定的情况下, 有一种泛型机制统一表示它们就很方便, 例如以下场景:
1 | class manager |
一旦调用1
2manager m;
m.startWork();
这是就会调用你传入的回调函数, 根本不必考虑回调的函数的形式. (因为返回值, 参数已经约定了回调形式)
(扩展就是, 你完全可以去写一个, 回调工厂, 包装各种类型的回调, 然后bind的时候绑定工厂类的成员方法, 即具体的回调)
Scott Meyers的Effective C++ 3rd ed.第35条款提到了以boost::function和boost:bind取代虚函数的做法
包装成员函数
以前说bind的时候, 就说过, 如果该可调用对象是成员函数, 那么就一定需要该类实例的引用或者指针才能绑定, 例如:1
std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
当然, 你可以直接使用 std::mem_fn
去绑定成员函数.
注意, 原来boost库中, 上面的语句, 在function声明的时候, 就要声明具体对象类型作为模板参数, 即1
2
3std::function<void(&Foo, int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
//当然只写函数声明, bind的时候才传入地址指针也是可以的, 并且是推荐的.
使用案例
直接上代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using namespace std;
std::function< int(int)> Functional;
// 普通函数
int TestFunc(int a)
{
return a;
}
// Lambda表达式
auto lambda = [](int a)->int{ return a; };
// 仿函数(functor)
class Functor
{
public:
int operator()(int a)
{
return a;
}
};
// 1.类成员函数
// 2.类静态函数
class TestClass
{
public:
int ClassMember(int a) { return a; }
static int StaticMember(int a) { return a; }
};
int main()
{
// 普通函数
Functional = TestFunc;
int result = Functional(10);
cout << "普通函数:"<< result << endl;
// Lambda表达式
Functional = lambda;
result = Functional(20);
cout << "Lambda表达式:"<< result << endl;
// 仿函数
Functor testFunctor;
Functional = testFunctor;
result = Functional(30);
cout << "仿函数:"<< result << endl;
// 类成员函数
TestClass testObj;
Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);
result = Functional(40);
cout << "类成员函数:"<< result << endl;
// 类静态函数
Functional = TestClass::StaticMember;
result = Functional(50);
cout << "类静态函数:"<< result << endl;
return 0;
}
auto问题
function作为泛型函数指针的升级, 由于封装, 所以性能比起原始指针会有一定的损失(具体可以再测试看看), 而和auto则没有太多的可比性.
因为 auto
提供的是编译时的类型推倒机制, 而function像一个容器, 或者包装器, 实现的是动态的推倒(处理运行时类型), 属于泛型编程的范畴; 这也就是说, auto没有运行时开销, 效率上要比function搞, 但是用于回调这种场景是不行的, 不具有灵活性.
尾巴
虽然比不上bind那么变态的适配能力, 但是 function
在一定程度上来说, 也差不多是万能适配了.
参考资料
- 《Effective C++ 3rd ed》 第35条consider alternatives to virtual functions 电子工业出版社 Page169
- 陈硕博文