可以直接接受`std :: bind`的输出作为值,而不转换为std :: function吗?

时间:2016-04-14 08:17:54

标签: c++ c++11 std-function stdbind

This answer表示std::bind按值返回一个对象,this comment表示分配给std::function将导致堆分配存储{{1}返回的值}}。

有没有办法避免这种堆分配并直接通过值将std::bind的返回值传递给另一个函数?

如果是这样,方法签名会将std::bind替换为什么?

为了更明确,我有一个类似以下的功能。

std::function

假设有一个带有以下签名的函数void runThisFunction(std::function<void()> func);

foo

现在,我会按如下方式调用void foo(int a, int b);

runThisFunction

在此调用中,runThisFunction(std::bind(foo, 1, 2)); 的输出转换为std::bind,动态内存分配作为此过程的一部分发生。

是否可以将std::function替换为可以直接通过值接收std::function输出的其他声明,从而避免动态内存分配?

4 个答案:

答案 0 :(得分:4)

  

是否可以将std::function替换为可以直接通过值接收std::bind输出的其他声明,从而避免动态内存分配?

是的;但std::bind返回的类型未指定,因此您需要使用模板来捕获类型;

template <typename F>
void runThisFunction(F func);

关于内存分配......

  

在此调用中,std::bind的输出转换为std::function,动态内存分配作为此过程的一部分发生。

可以使用动态内存(但不是always),它取决于绑定到std::function的仿函数的大小和实现的质量。

此外,C ++规范有§20.12.12.2.1/11;

  

[注意:鼓励实现避免为小型可调用对象使用动态分配的内存,例如,f是仅包含对象的指针或引用的对象以及成员函数指针。    - 结束说明]

即使有内存分配,我也不会太在意它。除非代码对性能至关重要,并且您已经对其进行了测量,否则所需的间接不应成为问题。

请记住,对于您的情况,foo中绑定的bind是指针,可能无论如何都不会有动态内存分配。

  

我开始研究这个问题,因为我通过仪器检测到意外的缓慢来测量转换上的缓存未命中

因此,您对性能有一些可测量的担忧......有使用std::bindstd::function配对的替代方法。 std::bind是有用的通用粘合剂,但这并不意味着它也具有足够的性能 - 制作您自己的。自定义函子可以更高效。基于lambda的实现也很好看。不要忘记函数foo也可以与std::function一起使用,然后你完全放弃仿函数/活页夹(需要注意的是签名)。

在上面的引用中提到的“小对象”优化之前,对象需要“小”的一个附注似乎在库实现之间有点不同。

coliru(libstdc ++)上,std::function的参数大小需要为16个字节或更少,在MSVC上,限制为32个字节(这两个都是是32位平台)。使用clang ++(libc ++)64位编译时,此限制为24个字节......实际取决于实现需要在new分配之前允许的空间。

我不确定性能有多重要,但也可以为目标计算此限制,然后应用优化,以使std::function的参数保持在这些限制之下;例如使用指针或引用(也std::ref)到struct的参数(但必须注意这些不是悬空)。

答案 1 :(得分:1)

如果性能很重要但仍需要具体的接口,请考虑绑定到lambda而不是binder对象(通过std :: bind)。

我在gcc 5.3(libstdc ++, - 02)

上运行了这个测试
#include <functional>

void foo(int, int);

void runThisFunction(std::function<void()> func);

void test()
{
  runThisFunction(std::bind(&foo, 1, 2));
}

void test1()
{
  runThisFunction([]{ foo(1, 2); });
}

test()会调用new。但是,std::function中的小函数优化能够检测到test1()中的lambda足够小,并且不会向代码中调用new

(注意:为清楚起见,删除了异常处理代码):

std::_Function_base::_Base_manager<test1()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<test1()::{lambda()#1}> const&, std::_Manager_operation):
        testl   %edx, %edx
        je      .L3
        cmpl    $1, %edx
        jne     .L2
        movq    %rsi, (%rdi)
.L2:
        xorl    %eax, %eax
        ret
.L3:
        movq    typeinfo for test1()::{lambda()#1}, (%rdi)
        xorl    %eax, %eax
        ret
std::_Function_handler<void (), test1()::{lambda()#1}>::_M_invoke(std::_Any_data const&):
        movl    $2, %esi
        movl    $1, %edi
        jmp     foo(int, int)
test1():
        subq    $40, %rsp
        movq    %rsp, %rdi
        movq    std::_Function_handler<void (), test1()::{lambda()#1}>::_M_invoke(std::_Any_data const&), 24(%rsp)
        movq    std::_Function_base::_Base_manager<test1()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<test1()::{lambda()#1}> const&, std::_Manager_operation), 16(%rsp)
        call    runThisFunction(std::function<void ()>)
        movq    16(%rsp), %rax
        testq   %rax, %rax
        je      .L7
        movl    $3, %edx
        movq    %rsp, %rsi
        movq    %rsp, %rdi
        call    *%rax
.L7:
        addq    $40, %rsp
        ret
typeinfo for test1()::{lambda()#1}:
typeinfo name for test1()::{lambda()#1}:

答案 2 :(得分:1)

通过使用引用包装器,您应该避免堆分配:

auto f = std::bind(foo, 1, 2);
runThisFunction(std::ref(f));

这是因为reference_wrapper是一个小对象,并鼓励std::function避免分配小(参见[func.wrap.func.con]):

  

注意:鼓励实施以避免动态使用   为小型可调用对象分配内存,例如f   target是一个只持有a的对象   指针或对象的引用和成员函数指针。

仅当runThisFunction()未在std::function的生命周期后存储f的值进行调用时,它才有效。

答案 3 :(得分:-1)

非常简单(至少对于gcc):

int someFunc(int a, int b) { return a+b; }
//....
int b = (std::bind(someFunc,3,4))(); // returns 7