使用std :: function或转发引用作为高阶函数的通用可调用对象输入参数?

时间:2016-12-05 11:23:30

标签: c++ c++11 std-function perfect-forwarding

我想知道编写更高阶函数的主要区别,优点和缺点,作为输入参数,使用std::function转发参考,例如template<typename F> void hof(F&& fun);。 显然,前者比后者更严格,因为它指定了输入可调用对象必须符合的函数类型

2 个答案:

答案 0 :(得分:4)

std::function通常会产生大量的运行时开销。通过template参数传递通用可调用对象可以避免std::function的间接成本,可以让编译器积极优化

我在the end of this article为lambda递归(Y-combinator vs std::function写了一些简单的基准测试。与非多态Y组合器实现相比,std::function总是生成至少3.5倍的汇编。这是一个不错的示例,它显示了std::functiontemplate参数更昂贵的方式。

我建议在gcc.godbolt.org上玩,看看两种技术之间的装配差异。

以下是I just came up with的例子:

#if defined(STDFN)
void pass_by_stdfn(std::function<void()> f)
{
    f();
}
#else
template <typename TF>
void pass_by_template(TF&& f)
{
    f();
}
#endif

volatile int state = 0;

int main()
{
#if defined(STDFN)
   pass_by_stdfn([i = 10]{ state = i; });
#else
   pass_by_template([i = 10]{ state = i; });  
#endif
}

如果STDFN 未定义,则生成的程序集为:

main:
        mov     DWORD PTR state[rip], 10
        xor     eax, eax
        ret
state:
        .zero   4

STDFN 定义,生成的程序集长48行。

答案 1 :(得分:3)

std::function有很多专业人士,但它也有一系列缺点,你必须考虑到这一点。
举个例子:

  • 可调用对象应该是可复制的 换句话说,这编译:

    #include <functional>
    #include <utility>
    #include <memory>
    
    template<typename F>
    void func(F &&f) { std::forward<F>(f)(); }
    
    int main() {
        func([ptr = std::make_unique<int>()](){});
    }
    

    但这不是:

    #include <functional>
    #include <utility>
    #include <memory>
    
    void func(std::function<void(void)> f) { f(); }
    
    int main() {
        func([ptr = std::make_unique<int>()](){});
    }
    
  • 尽管(强调我的):

      

    鼓励实现以避免为小型可调用对象使用动态分配的内存,例如,f的目标是仅包含对象的指针或引用的对象,以及成员函数指针。

    您无法保证在可以避免的情况下您不会对动态存储进行分配,并且您在所有其他情况下都可以确定分配。

  • 构建std::function时,构造函数可能会抛出bad_alloc

  • ......可能我们可以继续,但它不值得,你明白了。

std::function是一个非常有用的工具,但是当你需要时你应该使用它们。
例如,如果您打算在某处存储功能,可能最终会使用std::function。另一方面,如果您计划接受可调用对象并在运行中调用它,可能您不会使用std::function
这些仅仅是几个例子,不幸的是,黄金法则并不存在。