我只是针对 g++ 的情况对此进行了一些研究。
当谈到 std::function 和动态内存分配时,有两个关键点。
- std::function 可以存储任意大小的对象,这意味着它在某些情况下必须执行动态内存分配。
- 有某些类型保证 std::function 不会抛出异常。这意味着它必须在没有动态内存分配的情况下存储某些类型。
gccs libstd+++ 中 std::function 的实现将在没有动态内存分配的情况下存储其他大小/对齐要求小于或等于它必须存储的东西的大小/对齐要求的东西。
在没有动态内存分配的情况下,它必须存储的最大内容是指向成员函数的指针。在基于“itanium c++ ABI”* 的编译器上,这是普通指针大小的两倍。因此,您可以在 g++ 中的 std::function 中存储最多两个大小的指针,而不会触发动态内存分配。
据我所知,std::bind 只是将东西连接在一起形成一个对象,因此将任何东西绑定到成员函数将产生一个大小至少为三个指针的对象。将此对象分配给 std::function 将导致动态内存分配。
更好的选择是使用 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)
我们可以清楚地看到在 bind 情况下有内存分配,但在 lambda 情况下没有。
* 尽管名称被 g++ 和 clang++ 在许多不同的架构中使用。