g ++の場合について、これについて調査しました。
std :: functionと動的メモリ割り当てに関しては、2つの重要なポイントがあります。
- std :: functionは任意のサイズのオブジェクトを格納できます。つまり、場合によっては動的メモリ割り当てを実行する必要があります。
- std::functionが例外をスローしないことが保証されている特定のタイプがあります。これは、動的メモリ割り当てなしで保存する必要がある特定のタイプがあることを意味します。
gccslibstd+++でのstd::functionの実装は、動的メモリ割り当てなしで、格納する必要のあるもののサイズ/アライメント要件以下のサイズ/アライメント要件を持つ他のものを格納します。
動的メモリ割り当てなしで格納する必要がある最大のものは、メンバー関数へのポインタです。「itaniumc++ABI」*に基づくコンパイラでは、これは通常のポインタの2倍のサイズです。したがって、動的メモリ割り当てをトリガーせずに、最大2つのポインタのサイズをg++のstd::functionに格納できます。
std :: bindは、ものを1つのオブジェクトに連結するだけなので、メンバー関数に何かをバインドすると、少なくとも3つのポインターのサイズのオブジェクトになります。このオブジェクトをstd::functionに割り当てると、動的メモリ割り当てが行われます。
より良いオプションは、ラムダを使用することです。これは、メンバー関数を静的に参照し、動的メモリ割り当てをトリガーせずに最大2つのポインターをキャプチャするためのスペースを提供します。
実証するために、私はあなたのテストコードに大まかに基づいていくつかのテストコードを書きました。文字列とリストを削除し、代わりにconst char *(std :: string関連のメモリ割り当てを回避するため)と新しい配置(このコードはビルドのみを目的としており、実行することを目的としていません)を使用して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)
バインドの場合にはメモリ割り当てがあることがはっきりとわかりますが、ラムダの場合にはありません。
*この名前にもかかわらず、多くの異なるアーキテクチャでg++とclang++によって使用されています。