技术: C++11 可调用对象function总结

本文从其 实现使用, 以及和 auto, bind等关系, 上单独说一说 function .

如果说 bind 是适配器(现有的可调用对象不适用时), ref是引用封装, 那么 function 就相应与对于各种各种可调用对象(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的包装或者容器. 实际上深究一下, 它应该说成 “智能函数指针” .

从 boost 库 ‘boost/function.hpp’ 中, 引入标准库, function 完成了可调用对象的统一约束.

引子

既然是可调用对象, 那么指定参数类型和返回值是肯定的, 例如:

1
2
3
4
5
#include <functional>
std::function< int(int)> Functional;

//不进行初始化(空指针初始化), 直接调用的话会报异常
//bad_function_call

实际上, 有具体的两条规则:

  • 转换后的std::function对象的参数能转换为可调用实体的参数
  • 可调用实体的返回值能转换为std::function对象的返回值

正文

function声明

对于一个函数, 在声明时, 是一定要指定返回值类型和参数的(function类模板内部有 typedef 定义返回值和参数类型).

可能有的编译器可以递归推倒, 例如:

1
2
3
4
5
6
7
#include <functional>
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
#include <functional>
int func(double) { return 0; }

std::function<int(double)> f = func;
std::function<std::decltype(func)> f = func;

不要写成 std::decltype(&func) , 这样推倒出来的是函数指针类型(而非函数类型).

类摘要

function是一些列重载类模板(参数不同), 但是一般使用一个更加通用的类function.

1
2
emplate< 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <functional>
int f(int a, int b) { return a+b; }

int main(void)
{
function<int(int,int)> func;
assert(!func); //true, 因为此时还没有初始化

func = f;
assert(func.contains(f));

if (!func) {
func(10,20);
//( *( func.target() ) )(10,20);
}
func = 0; //相当于clear()
assert(func.empty());
}

function作用

统一调用形式的作用?
我们知道, 函数指针可以用于回调, 函数对象可以用于回调, lambda表示也可以, 甚至是bind适配后的成员函数也可以, 那么在不确定的情况下, 有一种泛型机制统一表示它们就很方便, 例如以下场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class manager
{
private:
std::function<void(int)> _cb;

public:
void startWork()
{
int i = 10;
if(_cb){
_cb(i);
}
}

void setCallback(std::function<void(int)> cb)
{
_cb = cb;
}
}

一旦调用

1
2
manager 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
3
std::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
#include <functional>
#include <iostream>
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 在一定程度上来说, 也差不多是万能适配了.

参考资料

  1. 《Effective C++ 3rd ed》 第35条consider alternatives to virtual functions 电子工业出版社 Page169
  2. 陈硕博文
文章目录
  1. 1. 引子
  2. 2. 正文
    1. 2.1. function声明
    2. 2.2. 类摘要
    3. 2.3. function作用
    4. 2.4. 包装成员函数
    5. 2.5. 使用案例
    6. 2.6. auto问题
  3. 3. 尾巴
  4. 4. 参考资料
|