std::函数和std::bind 做动态内存分配吗?

do std::function and std::bind do dynamic memory allocation?

本文关键字:std 分配 内存 函数 bind 动态      更新时间:2023-10-16

如果我这样做:-

class Thing  
{
...
void function (const std::string& message);
};
std::list<std::function<void()>> work;

并在"事物"的某些成员中

work.push_back(std::bind(&Thing::function, this, "Hello"));

调用 std::bind 或使用 std::function<> 是否会导致使用 new 或其他方式进行任何动态内存分配?还是所有存储都是在编译时分配的?如果标准没有说明什么,那么在Visual Studio 2012中呢,因为我的程序只需要在那里构建,为了提高效率,我可能需要避免在我考虑使用此机制的地方进行动态内存分配。

该标准没有指定,但总的来说,很容易看出std::function必须至少在某些情况下分配内存:

struct huge { char c[10000]; };
void foo(const huge &);
std::function<void()>{std::bind(foo, huge{})};

另一方面,至少在某些情况下,它可以通过将其函数对象放置在function对象占用空间内的预分配缓冲区内来避免分配;显然有一个权衡,因为这可能会使其他用途占用更多的堆栈内存。 一个好的实现在function对象中存储原始函数指针时能够避免内存分配,也可能用于mem_fn,但对于bind

,它不太可能这样做。例如,libstdc++ (g++) 内联(函子)对象指针、函数指针和(非虚拟)成员函数指针,以及适合相同占用空间的任何其他内容,例如无状态函子 (union _Nocopy_types)。

如果可以,通过反转控制流以接受模板化函子对象而不是function可以避免任何额外的内存分配:

template<typename F>
void my_algorithm(const F &);
my_algorithm(std::bind(foo, huge{}));

我只是针对 g++ 的情况做了一些研究。

当涉及到 std::function 和动态内存分配时,有两个关键点。

  1. std::函数可以存储任意大小的对象,这意味着它在某些情况下必须执行动态内存分配。
  2. 对于某些类型,std::函数保证不会引发异常。这意味着它必须在没有动态内存分配的情况下存储某些类型。

gccs libstd+++ 中 std::function 的实现将在没有动态内存分配的情况下存储其他大小/对齐要求小于或等于它必须存储的内容的大小/对齐要求的东西。

在没有动态内存分配的情况下,它必须存储的最大内容是指向成员函数的指针。在基于"itanium c++ ABI"*的编译器上,这是普通指针大小的两倍。因此,您可以在 g++ 的 std::function 中存储最多两个指针,而无需触发动态内存分配。

据我所知,std::bind 只是将东西连接成一个对象,因此将任何内容绑定到成员函数将导致对象的大小至少为三个指针。将此对象分配给 std::函数将导致动态内存分配。

更好的选择是使用 lambda。这是指静态成员函数,为您提供空间来捕获最多两个指针,而不会触发动态内存分配。

为了演示,我根据你的代码松散地编写了一些测试代码。我摆脱了字符串和列表,并使用了 const char *(以避免 std::string 相关的内存分配)和放置 new(此代码仅用于构建,而不是运行)并将其输入 godbolt。

#include <functional>
using namespace std;
class Thing  
{
void foo();
void bar();
void function (const char * message);
};
char baz[1024];
void Thing::foo() {
new (baz) std::function<void()>(std::bind(&Thing::function, this, "Hello"));
}

void Thing::bar() {
const char * s = "Hello";
new (baz) std::function<void()>([this,s](){function(s);});
}

结果是。

Thing::foo():
mov     r3, #0
push    {r4, r5, r6, lr}
ldr     r4, .L34
mov     r6, r0
sub     sp, sp, #16
mov     r0, #16
str     r3, [r4, #8]
bl      operator new(unsigned int)
ldr     r2, .L34+4
mov     r1, #0
mov     r3, r0
str     r2, [sp]
mov     r2, sp
ldr     r5, .L34+8
ldr     lr, .L34+12
ldr     ip, .L34+16
str     r1, [sp, #4]
str     r6, [r0, #12]
str     r0, [r4]
str     r5, [r3, #8]
ldm     r2, {r0, r1}
str     lr, [r4, #12]
stm     r3, {r0, r1}
str     ip, [r4, #8]
add     sp, sp, #16
pop     {r4, r5, r6, pc}
ldr     r3, [r4, #8]
cmp     r3, #0
beq     .L27
ldr     r1, .L34
mov     r2, #3
mov     r0, r1
blx     r3
.L27:
bl      __cxa_end_cleanup
.L34:
.word   .LANCHOR1
.word   Thing::function(char const*)
.word   .LC0
.word   std::_Function_handler<void (), std::_Bind<void (Thing::*(Thing*,     char const*))(char const*)> >::_M_invoke(std::_Any_data const&)
.word   std::_Function_base::_Base_manager<std::_Bind<void (Thing::*(Thing*, char const*))(char const*)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation)
Thing::bar():
ldr     r2, .L38
sub     sp, sp, #8
stm     sp, {r0, r2}
add     r2, sp, #8
ldr     r3, .L38+4
ldmdb   r2, {r0, r1}
ldr     ip, .L38+8
ldr     r2, .L38+12
stm     r3, {r0, r1}
str     ip, [r3, #12]
str     r2, [r3, #8]
add     sp, sp, #8
bx      lr
.L38:
.word   .LC0
.word   .LANCHOR1
.word   std::_Function_handler<void (), Thing::bar()::{lambda()#1}>::_M_invoke(std::_Any_data const&)
.word   std::_Function_base::_Base_manager<Thing::bar()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<Thing::bar()::{lambda()#1}> const&, std::_Manager_operation)

我们可以清楚地看到在绑定情况下有内存分配,但在 lambda 情况下没有。

* 尽管名称被 g++ 和 clang++ 用于许多不同的架构。

我不确定这一点。我想,正如 ecatmur 所建议的那样,这取决于该平台的 std 实施。对于类似的问题,我已经成功地从代码项目中使用此实现。它支持大量平台。文档非常详细,没有动态内存分配。

http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible

应避免在游戏或模拟运行时进行通用动态内存分配。问题并不总是碎片化或大瓶颈(两者都是要避免的正当理由),而是时间量通常是不确定的。更特定于域的内存分配策略(如"池化"或"成帧")在这里将是有利的。

http://g.oswego.edu/dl/html/malloc.html