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

Is it possible to accept the output of `std::bind` directly as a value, without conversion to std::function?

This answer states that std::bind returns an object by value, and this comment 意味着分配给 std::function 将导致堆分配存储由 std::bind 编辑的值 return。

有没有办法避免这种堆分配,将std::bind的return值直接按值传递给另一个函数?

如果是这样,方法签名将用什么替换 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 替换为其他声明,这些声明将直接按值接收 std::bind 的输出,从而避免动态内存分配?

Is it possible to replace std::function with some other declaration that would would receive the output of std::bind directly by value, thereby avoiding the dynamic memory allocation?

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

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

关于内存分配...

In this invocation, the output of std::bind is converted into an std::function, and dynamic memory allocation occurs as part of this process.

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

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

[Note: Implementations are encouraged to avoid the use of dynamically allocated memory for small callable objects, for example, where f is an object holding only a pointer or reference to an object and a member function pointer. — end note ]

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

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


I started looking at this because I measured cache misses on the conversion due to unexpected slowness detected through instrumentation

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


关于 "small" 对象需要如何 在上面引用中提到的 "small object" 优化之前的旁注似乎在库实现有点安静。

这里 coliru (libstdc++), the size of the argument for std::function needs to be 16 bytes or less, on MSVC,限制是 32 字节(这两个看起来都是 32 位平台)。使用 clang++ (libc++) 64 位编译,这个限制是 24 字节......这真的取决于实现,在需要进行 new 分配之前,它们将允许多少 space。

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

如果性能很重要,但您仍然想要一个具体的接口,请考虑绑定到 lambda 而不是活页夹对象(通过 std::bind)。

我 运行 这个测试是在 gcc 5.3 (libstdc++, -O2)

#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    , %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    , %esi
        movl    , %edi
        jmp     foo(int, int)
test1():
        subq    , %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    , %edx
        movq    %rsp, %rsi
        movq    %rsp, %rdi
        call    *%rax
.L7:
        addq    , %rsp
        ret
typeinfo for test1()::{lambda()#1}:
typeinfo name for test1()::{lambda()#1}:

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

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

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

Note: Implementations are encouraged to avoid the use of dynamically allocated memory for small callable objects, for example, where f’s target is an object holding only a pointer or reference to an object and a member function pointer.

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

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

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