C++中的std::function到底是什么,为什么我们需要它?
创建时间:2021年1月18日最近修改时间:2021年12月19日
昨天,有人在#include<C++>的discord服务器上问了关于为社么我们需要std::function的问题。
下面是我对这个问题的回答。
即使参数类型以及返回类型都完全相同,C++中的可以被当作函数一样被调用的对象也可以有不同的类型
Lambda表达式可以被认为是定义有operator()的类的语法糖。比如说
int x = 3;auto lambda = [x](int y) { return x + y; };大体上等同于
struct __Lambda { int x;
int operator()(int y) const { return x + y; }};
int x = 3;auto lambda = __Lambda { .x = x };因此,每个lambda表达式都有一个独特的类型。例如,在下面的片段中,
int x, z;
auto lambda = [x](int y) { return x + y; };auto lambda2 = [x, z](int y) { return x + y + z; };尽管lambda和lambda2都接收一个int并返回一个int,它们有着不一样的类型。
C++还有普通的函数,
它们又和实现了operator()的类不同。
对std::function的需求
被当作函数一样被调用的对象
那么,我们如何存储一个接收一个int并返回一个int的一个可调用的对象,并且不考虑它的具体类型?
我们需要std::function来完成这样的任务。比如说:
struct S { std::function<int(int)> func;};以这种方式存储可调用程序的典型用例是一个task system。 你可能想在一个容器中存储回调,以便于以后执行:
struct TaskQueue { std::queue<std::function<void()>> queue; std::mutex mutex; std::condition_variable ready;
// 我省略了各个成员函数的实现 ...};类型擦除(Type Erasure)
为了使func同时接受lambda和lambda2,
std::function需要有可以接受任何符合要求的函数对象或普通函数的构造函数。
我们需要使用类型擦除来实现这种行为。
在C++中实现类型擦除的方法有很多,不过这对于这篇文章来说有些超纲。
大体上的想法是,std::function需要储存一个函数指针以及另外一些用于存放lambda捕获的空间。
因为lambda表达式(或函数对象)可以有任意大小的捕获,这些额外的数据需要在堆上分配。
不过,所有主要的std::function实现都会进行小缓冲区优化(small buffer optimization),如果你的lambda小到可以装入预定义的容量。
在这种情况下,所有的数据都可以直接在std::function对象本身内部分配,而不需要进行额外的堆分配。