std :: function的替代品,用于收集可调用对象

Ziz*_*Tai 4 c++ templates c++11 c++14

除了求助之外,还有没有其他方法可以存储同类的可调用对象集合std::function?即,T在以下代码中替换类型

using T = std::function<void(int)>;
std::vector<T> v{some_lambda, some_fn_ptr, some_pmf, some_functor};
Run Code Online (Sandbox Code Playgroud)

还有别的吗?

当将各个可调用对象作为高阶函数的参数传递时,我尽可能使用template以避免的开销std::function。但是对于收藏,我不知道有什么我可以做的。

Yak*_*ont 5

直接类型最大的开销减少源是内联函数的能力。在重复应用的紧密循环中,有时可以对内联函数进行矢量化处理,或者进行大量优化。

开销的第二个来源std::function是它使用虚拟函数表的事实。这将导致两个“随机访问”内存查找-一个用于vtable,另一个用于跟随vtable上的指针。如果这些不是以前使用的高速缓存中的内容,则相当昂贵。但是,如果它们在高速缓存中,则最终一点都不昂贵(一些指令)。

开销的最后一个来源std::function是内存分配。如果存储在中的对象std::function很大(例如,在MSVC中,大于sizeof(std::string)*2,其中对象std::string本身使用SBO,因此其大小适中),则会发生堆分配。因此,无论何时std::function复制或创建,都会有相当高的成本。

这些都可以缓解。

定制std::function克隆可以使用无vtable的调用类型擦除来减少#2的成本。这具有尺寸成本。

一个function_ref不存储调用类型可写。它们存储void*和等价的vtable(或指向方法的直接指针)。或者,std::function可以编写一个具有自定义存储大小并拒绝堆分配的克隆。要么以灵活性和/或缺乏价值语义为代价合理地缓解了#3。

首先是最难缓解的。

如果您知道将使用可调用对象执行哪些操作,则可以擦除上下文中的调用,而无需擦除常规调用。

例如,假设您有一个逐像素操作。std::function在图像的每个像素上调用都会有很多开销。

但是,如果我们不删除(或同样)删除每个像素的调用,而是删除每个像素运行的调用,则现在可以通用地存储我们的可调用对象,而开销则从每个像素到每个扫描线或每个图片!

现在,可调用对象在紧密循环中可见,因此编译器可以对其进行内联和向量化,并且对vtable的跟踪工作仅在每个扫描行完成一次。

您可能会觉得更奇特,甚至还可以用排线擦除擦除扫描线。或进行一些擦除,一个用于具有零额外线距的扫描线,一个用于上下扫描线,另一个用于非零线距,等等。并且扫描线长度为2的幂。

这些都有成本,至少在开发时如此。仅当您已经测试并确认std::function确实引起问题时,才沿着这条路线走。