如何解决在执行期间用 std::bind 重新分配 std::function
我正在尝试在我的代码中实现一个逻辑,如果某种方法失败,我有几个备份机制。
class Foo
{
public:
Foo() { func = std::bind(&Foo::f1,this); }
void f1()
{
func = std::bind(&Foo::f2,this);
// ... (computation which may or may not produce a result)
bool computation_succeeded = false;
if(computation_succeeded)
result_produced = true;
}
void f2()
{
func = std::bind(&Foo::f3,this);
// ... (computation which may or may not produce a result)
bool computation_succeeded = false;
if(computation_succeeded)
result_produced = true;
}
void f3()
{
//computation which for sure produce a result
result_produced = true;
}
void execute()
{
while(!result_produced)
{
func();
}
}
std::function<void()> func;
bool result_produced{false};
};
int main()
{
Foo f;
f.execute();
}
基本上通过调用 f.execute() 首先尝试“方法”f1,然后,如果它没有产生结果(出于各种可能的原因),它将通过将该函数绑定到相同的 func 变量来尝试方法 f2,以及 f3 的相同推理(肯定会产生结果)。
解决方法
我读到将某些内容绑定到 std::function 时可能会发生动态内存分配。以这种方式重新绑定会导致任何与内存相关的问题吗?
确实如此,std::function
可能效率低下。如果不确定,请检查生成的程序集。
gcc 10.2 -O3
结果:godbolt link(注意:clang 结果相似)
主要:
push r12
mov edi,24
push rbp
sub rsp,88
mov QWORD PTR [rsp+48],0
mov BYTE PTR [rsp+64],0
mov QWORD PTR [rsp+16],0
call operator new(unsigned long)
mov QWORD PTR [rsp],rax
movdqa xmm0,XMMWORD PTR [rsp]
lea rbp,[rsp+32]
mov ecx,OFFSET FLAT:std::_Function_handler<void (),std::_Bind<void (Foo::*(Foo*))()> >::_M_manager(std::_Any_data&,std::_Any_data const&,std::_Manager_operation)
movdqa xmm1,XMMWORD PTR [rsp+32]
mov rdx,QWORD PTR [rsp+56]
mov QWORD PTR [rax+16],rbp
mov QWORD PTR [rax],OFFSET FLAT:Foo::f1()
mov QWORD PTR [rax+8],0
mov rax,QWORD PTR [rsp+48]
movaps XMMWORD PTR [rsp+32],xmm0
movq xmm0,rcx
movhps xmm0,QWORD PTR .LC0[rip]
mov QWORD PTR [rsp+16],rax
mov QWORD PTR [rsp+24],rdx
movaps XMMWORD PTR [rsp],xmm1
movaps XMMWORD PTR [rsp+48],xmm0
test rax,rax
je .L48
mov edx,3
mov rsi,rsp
mov rdi,rsp
call rax
jmp .L48
.L68:
test rax,rax
je .L67
mov rdi,rbp
call [QWORD PTR [rsp+56]]
.L48:
cmp BYTE PTR [rsp+64],QWORD PTR [rsp+48]
je .L68
test rax,rax
je .L59
mov edx,rbp
mov rdi,rbp
call rax
.L59:
add rsp,88
xor eax,eax
pop rbp
pop r12
ret
.L67:
call std::__throw_bad_function_call()
mov rbp,rax
jmp .L44
mov r12,rax
jmp .L52
main.cold:
.L44:
mov rax,QWORD PTR [rsp+16]
test rax,rax
je .L45
mov edx,rsp
call rax
.L45:
mov rax,QWORD PTR [rsp+48]
test rax,rax
je .L47
lea rsi,[rsp+32]
mov edx,3
mov rdi,rsi
call rax
.L47:
mov rdi,rbp
call _Unwind_Resume
.L52:
mov rax,rax
je .L53
mov edx,rbp
call rax
.L53:
mov rdi,r12
call _Unwind_Resume
所以我们已经看到动态分配、异常处理和 Foo::f1
没有内联...
Foo::f1():
push rbp
push rbx
mov rbx,rdi
mov edi,24
sub rsp,40
mov QWORD PTR [rsp+16],XMMWORD PTR [rsp]
mov ecx,std::_Manager_operation)
movdqu xmm1,XMMWORD PTR [rbx]
mov rdx,QWORD PTR [rbx+24]
mov QWORD PTR [rax+16],rbx
mov QWORD PTR [rax],OFFSET FLAT:Foo::f2()
mov QWORD PTR [rax+8],QWORD PTR [rbx+16]
movups XMMWORD PTR [rbx],xmm1
movups XMMWORD PTR [rbx+16],rax
je .L33
mov edx,rsp
call rax
.L33:
call do_actual_work()
mov BYTE PTR [rbx+32],al
add rsp,40
pop rbx
pop rbp
ret
mov rbp,rax
mov rax,rax
je .L35
mov edx,rsp
call rax
.L35:
mov rdi,rbp
call _Unwind_Resume
是的,又是内存分配、条件跳转、动态调度和异常处理...
Foo::f2():
push rbp
push rbx
mov rbx,OFFSET FLAT:Foo::f3()
mov QWORD PTR [rax+8],rax
je .L23
mov edx,rsp
call rax
.L23:
call do_actual_work()
mov BYTE PTR [rbx+32],rax
je .L25
mov edx,rsp
call rax
.L25:
mov rdi,rbp
call _Unwind_Resume
同样的故事。
Foo::f3():
push rbx
mov rbx,rdi
call do_actual_work()
mov BYTE PTR [rbx+32],1
pop rbx
ret
好吧好多了,这里没什么可看的。
现在让我们试试没有 std::function
...
bool do_actual_work();
class Foo
{
public:
Foo() { }
bool f1()
{
return do_actual_work();
}
bool f2()
{
return do_actual_work();
}
bool f3()
{
return do_actual_work();
}
void execute()
{
if (f1())
return;
if (f2())
return;
f3();
}
};
int main()
{
Foo f;
f.execute();
}
生成的程序集:godbolt link
main:
sub rsp,8
call do_actual_work()
test al,al
je .L7
.L3:
xor eax,eax
add rsp,8
ret
.L7:
call do_actual_work()
test al,al
jne .L3
call do_actual_work()
jmp .L3
这就是整个程序!
故事的寓意:除非不可避免,否则不要使用 std::function
。例如,当您编写共享库并且需要以通用方式获取用户提供的回调时,这是不可避免的。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。