在 C/C++ 中函数指针作为一种回调机制被广泛使用, 但是函数指针在 C++ 面向对象编程中有些不足, 比如无法捕捉上下文举个例子, 使用对象的非静态成员函数作为函数指针就无法做到
仿函数
在 C++11 之前, 我们在使用 STL 算法时, 通常会使用到一种特别的对象, 称为函数对象, 或者仿函数(functor), 例子如下:
- class _functor {
- public:
- int operator()(int x, int y) { return x + y; }
- };
- int main() {
- int girls = 3, boys = 4;
- _functor totalChild(5, 6);
- return totalChild(5, 6);
- }
简单地说, 仿函数就是重新定义了成员函数 operator()的一种对象, 其使用在代码层面感觉跟函数的使用并无二样, 但究其本质却并非函数
相比函数, 仿函数可以拥有初始状态, 一般通过 class 定义私有成员, 并在声明对象的时候对其进行初始化私有成员的状态就成了仿函数的初始状态声明一个仿函数对象可以拥有多个不同初始状态的实例, 因此可以借由仿函数产生多个功能类似却不同的仿函数实例
- #include <iostream>
- using namespace std;
- class Tax {
- private:
- float rate;
- int base;
- public:
- Tax(float r, int b) : rate(r), base(b) {}
- float operator() (float money) { return (money - base) * rate; }
- };
- int main() {
- Tax high(0.40, 30000);
- Tax middle(0.25, 20000);
- cout << "tax over 3w:" << high(37500) << endl;
- cout << "tax over 2w:" << middle(27500) << endl;
- return 0;
- }
- std::function
在上一篇文章中我们介绍了 C++11 中的 lambda 函数 lambda 函数在本质上并非函数, 这样导致一个问题:
函数指针不能指向 lambda 函数, 因为 lambda 函数本质上并非函数
仿函数和函数指针及 lambda 函数类型也不相同
当然上述问题也不是没有解决方法, 通过 C++ 模板 (template) 就可以, std::sort 的实现就使用了模板, 不论使用函数仿函数还是 lambda 函数实现排序算法, 均可以传给 std::sort
但是采用模板最大的问题在于编译期展开, 头文件会变得很大, 编译时间也会很长
C++11 引入 std::function 更好的解决了这一问题
std::function 可以用于保存并调用任何可调用的东西, 比如函数 lambda 函数 std::bind 表达式仿函数, 甚至是指向对象成员的指针
std::function 简单来说就像是个接口, 且能够把符合这个接口的对象 (这里对象泛指一切类型, 并非面向对象编程中的对象) 储存起来, 更神奇的是, 两个 std::function 的内容可以交换
下面的示例演示了将函数指针 lambda 函数和 std::bind 表达式传递给 std::function:
- int add(int a, int b) { return (a + b); }
- int sub(int a, int b) { return (a - b); }
- int mul(int a, int b) { return (a * b); }
- class Math
- {
- public:
- int mod(int a, int b) { return (a % b); }
- };
- int compute(int a, int b, std::function op) { return op(a, b); }
- int main()
- {
- compute(1, 2, add);
- compute(1, 2, sub);
- compute(1, 2, mul);
- compute(1, 2, [](int a, int b) -> int { return (a % b); });
- Math math;
- compute(1, 2, std::bind(&Math::mod, &math, std::placeholders::_1, std::placehoders::_2));
- return 0;
- }
需要指出的是, 所谓符合接口, 并不需要参数和返回值声明完全一致, 只要能够通过隐式转换变成相同类型就可以了所以 std::function
- double divide(double a, double b) { return (a / b); }
- compute(1, 2, divide);
从上面的例子可以看出, std::function 可以应用的范围很广, 而且没有模板带来的头文件膨胀问题, 非常适合取代函数指针然而, std::function 相较于函数指针, 性能上会有一点点损失, 如果不是在性能特别关键的场合, 还是大胆拥抱 C++ 11 这一新特性吧!
参考
Should I use std::function or a function pointer in C++?
簡介 std::function (C++11 後的新功能)
深入理解 C++11 :C++11 新特性解析与应用, p238 ~ p240
WHO CALLS WHO? CALLBACKS IN C++11
来源: http://blog.csdn.net/mogoweb/article/details/79446904