std::function
是在 C++11 中新增的一个用于统一包装可调用对象的模板类型. 所谓统一包装, 就是无论被包装的内容的实际类型, 只要符合相应的函数调用签名, 都可以装入一个 std::function
对象中使用. 比如
#include
#include
// 全局函数
int fn_ptr(int x, int y)
{
return x + y;
}
// 包含 2 个 int 成员的函数对象类型
struct TwoInts {
TwoInts(int m_, int n_)
: m(m_)
, n(n_)
{}
int operator()(int x, int y)
{
return x + y + m + n;
}
int m;
int n;
};
int main()
{
// 使用 std::function 类型包装全局函数指针
std::function f(fn_ptr);
std::cout << f(1, 2) << std::endl; // 输出 3
// 使用 std::function 类型包装函数对象
std::function g(TwoInts(10, 20));
std::cout << g(1, 2) << std::endl; // 输出 33
return 0;
}
上面的使用例子中, 两个 std::function
对象定义都在栈上. 按照 C++ 的常识, 两个对象一定有相同的尺寸, 即对它们求 sizeof
得出的值一定相等. 但用于构造这两个 function
对象的材料却有着不同的尺寸, 也就是说 function
可以 "捕获" 任何尺寸的可调用对象, 这正是其奇妙之处.
下面就来简单分析 std::function
的实现方法.
虽然 std::function
是在 C++11 中引入的, 但作为一个基本实现的分析, 本文将排除所有 C++11 的特性以避免不必要的解释. 当然, 这样会产生一个硬伤: 由于可变参数模板特性也是 C++11 中引入的特性, 本文的实现中将不支持任意多个模板类型参数, 而是使用返回值类型加上 2 个参数的类型共计 3 个类型作为模板的类型参数列表. 亦即, 在 C++11 中, 下面的用法是可能的
std::function f; // 只有返回值类型 的特化
std::function g; // 有返回值类型和 1 个参数类型 的特化
std::function h; // 有返回值类型和 2 个参数类型 的特化
// 可以扩展为任意多个参数类型的特化, 这是 C++11 的新特性
而本文中要实现的只包含下面这样的形式
// 默认特化没有实现
template
class function;
// 实现有返回值类型和 2 个参数类型的偏特化
template
class function {
// ...
};
语法上, 类似上面的 function
, class function
等类似函数签名的模板特化形式并不常见, 虽然它是 C++11 之前就一直存在的语法. 抛开语法层面的部分, function
实现中最重要的就是如何在内部维护不同类型不同尺寸的可调用对象.
这一功能的实现显然需要堆分配. 也就是说, 在 function
结构内部会有一个指针, 指向某一可调用的对象. 但由于这一可调用对象是编译时不可确定的, 因此又必须包含某种运行时动态的部分. 这样的话方案基本就敲定了: 构造一个抽象基类, 然后 function
对象持有这个基类的指针就行了.