微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在执行期间用 std::bind 重新分配 std::function

如何解决在执行期间用 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 的相同推理(肯定会产生结果)。

这段代码编译通过,但我有两点疑问:

  • 在执行 func 变量时将其重新绑定到新函数会导致未定义的行为吗?
  • 我读到将某些内容绑定到 std::function 时可能会发生动态内存分配。以这种方式重新绑定会导致任何与内存相关的问题吗?

解决方法

我读到将某些内容绑定到 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 举报,一经查实,本站将立刻删除。