复制一个 std::function 有多贵?

How expensive is to copy an std::function?

虽然std::function是可移动的,但在某些情况下是不可能或不方便的。复制它会受到严重处罚吗?

它是否可能取决于捕获变量的大小(如果它是使用 lambda 表达式创建的)?它取决于实现吗?

它确实依赖于实现。它还取决于您在 std::function 中存储的内容。它肯定不像简单地复制 C 风格的函数指针那么便宜。

最好的办法是尝试按照您认为清晰方便的方式编写代码,然后,如果它 运行 不够快,请对其进行概要分析。如果你需要极致的性能,你可能会发现 std::function 根本不适合。

std::function 通常实现为值语义、小缓冲区优化、虚拟调度、类型擦除 class.

这意味着如果你的状态很小,复制将不涉及堆分配(除了在状态的复制构造函数内)和一些间接的(找到如何复制这个特定状态)。

如果您的状态很大(例如,在当前 MSVC 上大于两个 std::string),则需要额外的堆分配来存储状态。

这不是您想在每帧每个像素的基础上做的事情,但它并不是非常昂贵。

您的特定编译器和库版本如何实现 std::function 可能会有所不同,但我不知道与上述版本有任何显着差异。

所以,在下面的代码中:

std::function<std::string()> f = [s = "hello"s]{ return s; };

复制 f 将涉及 MSVC 上的 0 堆分配。

鉴于此代码:

std::function<std::string()> g = [a = "a"s, b = "b"s, c = "c"s]{ return a+b+c; };

是否需要堆分配才能复制 g

(是的,这两个都是非常愚蠢的函数对象。)

std::function 中的某些情况(函数指针、成员函数指针)应用小型缓冲区优化 (SBO) 是有效的要求,因为它们预计不会在分配内存时失败.一旦编写了一个 SBO 案例,使其更通用并不难,因此大多数 std::function 实现将小对象 "inside themselves" 和较大的对象存储在堆上。

然而,这个阈值没有被标准指定,它对性能成本很重要,所以如果性能真的很重要,你就必须分析以确保你的实现做到了。