【问题标题】:Is it possible to accept the output of `std::bind` directly as a value, without conversion to std::function?是否可以直接接受`std::bind`的输出作为值,而不转换为std::function?
【发布时间】:2016-04-14 08:17:54
【问题描述】:

This answer 声明std::bind 按值返回对象,this comment 暗示分配给std::function 将导致堆分配存储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 替换为将直接按值接收std::bind 输出的其他声明,从而避免动态内存分配?

【问题讨论】:

  • template &lt;class T&gt; void runThisFunction(T func) 算不算?
  • @PiotrSkotnicki,感觉它可能会起作用。它确实遇到了模板的常见问题(为每种类型生成一个代码副本),但我想它无济于事。
  • 不知道为什么这个问题被否决了。我根本没有想到使用模板来解决这个问题,尽管事后看来很明显。
  • 如果您不喜欢模板,并且调用者的变化不足以真正需要它们,您可以尝试void runThisFunction(decltype(std::bind(foo, 1, 2))&amp; func); - 我怀疑您可以使用任何@987654339 调用它@ 调用,其中参数的静态类型匹配 foo12,但您必须深入研究标准以查看是否有保证。
  • 你多久运行一次这个函数?相当于每帧每像素或类似? (比如几百万次/秒?)

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


【解决方案1】:

是否可以将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 是一个只包含一个指针或对一个对象的引用和一个成员函数指针的对象。 ——尾注]

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

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


我开始研究这个是因为我测量了由于通过检测检测到意外缓慢而导致的转换缓存未命中

所以你对性能有一些衡量的担忧......除了使用std::bindstd::function 之外,还有其他选择。 std::bind 是有用的通用活页夹,但这并不意味着它也足够高性能 - 自己制作。自定义函子可能会更高效。基于 lambda 的实现也很值得一看。不要忘记函数 foo 也可以与 std::function 一起使用,然后您完全放弃仿函数/绑定器(注意,签名需要匹配)。


在上面引用中提到的“小对象”优化启动之前,关于对象需要有多“小”的旁注似乎在库实现之间略有不同。 p>

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

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

【讨论】:

  • 我开始研究这个是因为我测量了由于通过检测检测到意外缓慢而导致的转换缓存未命中。我认为这个答案会起作用,但我明天测试后会接受。谢谢!
  • 这个答案有效,但是使接收函数模板化结果比我预期的要痛苦,因为这意味着将大量状态拉入我最初巧妙地隐藏在 cc 文件中的头文件中.
  • 这是缺点之一,未指定的类型倾向于这样做。我想说你可以调查重新设计,但那完全是另一个问题,也可能是矫枉过正。
【解决方案2】:

如果性能很重要,但您仍需要具体接口,请考虑绑定到 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    $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}:

【讨论】:

  • std::function 中不分配的保证适用于指针和reference_wrapper(鼓励用于小对象)。您的 lambda 没有在此处捕获任何内容(因此可能会在函数指针中进行转换)。捕获可能会有所不同。
  • @Jarod42 是的,根据标准库的实现,小函数优化会限制其积极性。但是,我无法推测 OP 可能会遇到的未来场景 - 这是他提出的场景。
  • 有趣的是 lambda 在某些情况下表现更好,考虑到我切换到 std::bind 是因为 lambda didn't work under gcc
  • @merlin2011 很有趣,但是您看到了解决方法吗?按值传递可调用?我原以为复制省略意味着它不会变慢。
  • @RichardHodges,我专门使用了std::bind,因为带有可变参数的 lambda 无法编译。
【解决方案3】:

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

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

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

注意:鼓励实现避免使用动态 为小的可调用对象分配的内存,例如,其中 f 的 target 是一个对象,它只包含一个 指向对象的指针或引用以及成员函数指针。

只有在runThisFunction() 没有存储std::function 的值以便在f 的生命周期之后调用时才会起作用。

【讨论】:

  • std::ref 的简洁替代方案。我不认为函数的简单调用就是runThisFunction 所做的全部,因此这需要注意绑定f 需要至少在runThisFunction 需要@ 时保持活动状态987654331@(以及任何其他runThisFunction 将函数提供给,例如某种列表)。
【解决方案4】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-12
    • 1970-01-01
    • 2021-09-18
    • 1970-01-01
    相关资源
    最近更新 更多