C++ 可调用函数集合的std::函数的替代方案

C++ 可调用函数集合的std::函数的替代方案,c++,c++11,templates,c++14,C++,C++11,Templates,C++14,除了求助于std::function,还有其他方法来存储同质的可调用集合吗?即,在以下代码中替换类型T using T = std::function<void(int)>; std::vector<T> v{some_lambda, some_fn_ptr, some_pmf, some_functor}; 使用T=std::函数; 向量v{some_lambda,some_fn_ptr,some_pmf,some_函子}; 还有别的吗 当将单个可调用项作为参数传递

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

using T = std::function<void(int)>;
std::vector<T> v{some_lambda, some_fn_ptr, some_pmf, some_functor};
使用T=std::函数;
向量v{some_lambda,some_fn_ptr,some_pmf,some_函子};
还有别的吗


当将单个可调用项作为参数传递给高阶函数时,我尽可能使用模板来避免
std::function
的开销。但是对于集合,我不知道我是否能做些什么。

仍然允许您在此处指定的所有备选方案

向量v{some_lambda,some_fn_ptr,some_pmf,some_函子};
您当然可以创建自己的模板声明,以满足所有这些需求。尽管我看不出它如何比
std::function
更有效或更好,除非您对该类型进行更多限制。

std::function的开销之所以存在,是因为它是一种值类型。它在内部存储你给它的函子的副本(当然它可以从中移动)。它可以进行类型擦除,但由于它可以存储任意一方的对象,因此它必须能够分配内存来执行此操作

因此,如果您的需求不要求函数实际存储一个对象,如果它只能引用一个将继续存在的对象,那么您就可以了。您可以找到几种此类类型的实现,通常称为
function\u ref
。这种类型从不分配内存;虽然它们的开销不是零,但也没有
函数
那么大

但是,这些是对现有函数的引用。对于函子,它们引用一个真实的C++对象(而不是函数/成员指针)。这就产生了一个潜在的终身问题。对于立即回调(您将它们传递给只在回调期间调用该回调的函数),这不是什么大问题。但在你的情况下,它可能不会有用


最终,如果您无法解决生存期问题,那么使用
std::function
的开销实际上对您很有用。所以最好接受它。

直接类型最大的开销减少来源是内联函数的能力。在重复应用的紧密循环中,内联函数有时可以矢量化或以其他方式获得大规模优化

std::function
的第二个开销来源是它使用虚拟函数表。这会导致两个“随机访问”内存查找——一个用于vtable,另一个跟随vtable上的指针。如果先前使用的缓存中没有这些内容,那么它的成本是合理的。然而,如果它们在缓存中,这最终不会花费太多(一些指令)

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

每一个问题都可以缓解

定制的
std::function
克隆可以使用vtable-less调用类型擦除来降低#2的成本。这是有成本的

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

第一个问题最难缓解

如果您知道将使用可调用函数执行哪些操作,那么您可以在上下文中删除到调用,而不是删除到泛型调用

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

但是,如果我们不删除每像素调用(或者也不删除),而是删除每像素运行调用,我们现在可以一般地存储我们的可调用项,并且开销从每像素到每扫描线或每图像

现在,可调用项在紧循环中可见,因此编译器可以对其进行内联和向量化,并且vtable的后续工作在每个扫描行中只执行一次

你可以变得更花哨,甚至让它用linestride擦除扫描线。或者进行一些擦除,一个用于零额外线间距的扫描线,一个用于倒置扫描线,另一个用于非零线间距,等等,扫描线长度为2的幂次方


这些都有成本,至少在开发时是如此。只有在您测试并确认
std::function
确实导致了问题时,才可以使用此方法。

如果所有可调用函数都是普通函数,只需使用普通的旧函数指针。您认为
std::function
的开销是多少?您能详细说明一下吗?能够存储不同的可调用类型是
std::function
设计的目的。您希望如何实现内联等功能?如果您想要运行时动态行为(
std::vector
),您必须为此付费。否则你可以使用一个
元组
/模板…你在寻找奇迹吗?没有。做任何事情的东西都不会比
std::function
便宜。
 std::vector<T> v{some_lambda, some_fn_ptr, some_pmf, some_functor};