【问题标题】:Reassign std::function with std::bind during execution在执行期间用 std::bind 重新分配 std::function
【发布时间】:2021-03-24 15:04:12
【问题描述】:

我正在尝试在我的代码中实现一个逻辑,如果某种方法失败,我有几个备份机制。

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 时可能会发生动态内存分配。以这种方式重新绑定是否会导致任何与内存相关的问题?

【问题讨论】:

  • @mch 我知道,这只是为了举例。无论如何,我编辑了代码以使其更清晰并且能够编译。
  • 我不知道为什么我的评论被删除了,但我再说一遍:你的例子不需要std::function

标签: c++ std-function stdbind


【解决方案1】:

我读到,将某些内容绑定到 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], 0
        mov     rax, QWORD PTR [rsp+48]
        je      .L68
        test    rax, rax
        je      .L59
        mov     edx, 3
        mov     rsi, 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, 3
        mov     rsi, rsp
        mov     rdi, 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, QWORD PTR [rsp+48]
        test    rax, rax
        je      .L53
        mov     edx, 3
        mov     rsi, rbp
        mov     rdi, 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], 0
        call    operator new(unsigned long)
        mov     QWORD PTR [rsp], rax
        movdqa  xmm0, XMMWORD PTR [rsp]
        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)
        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], 0
        mov     rax, QWORD PTR [rbx+16]
        movups  XMMWORD PTR [rbx], 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
        movups  XMMWORD PTR [rbx+16], xmm0
        test    rax, rax
        je      .L33
        mov     edx, 3
        mov     rsi, rsp
        mov     rdi, 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, QWORD PTR [rsp+16]
        test    rax, rax
        je      .L35
        mov     edx, 3
        mov     rsi, rsp
        mov     rdi, rsp
        call    rax
.L35:
        mov     rdi, rbp
        call    _Unwind_Resume

哎呀,又是内存分配、条件跳转、动态调度异常处理......

Foo::f2():

        push    rbp
        push    rbx
        mov     rbx, rdi
        mov     edi, 24
        sub     rsp, 40
        mov     QWORD PTR [rsp+16], 0
        call    operator new(unsigned long)
        mov     QWORD PTR [rsp], rax
        movdqa  xmm0, XMMWORD PTR [rsp]
        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)
        movdqu  xmm1, XMMWORD PTR [rbx]
        mov     rdx, QWORD PTR [rbx+24]
        mov     QWORD PTR [rax+16], rbx
        mov     QWORD PTR [rax], OFFSET FLAT:Foo::f3()
        mov     QWORD PTR [rax+8], 0
        mov     rax, QWORD PTR [rbx+16]
        movups  XMMWORD PTR [rbx], 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
        movups  XMMWORD PTR [rbx+16], xmm0
        test    rax, rax
        je      .L23
        mov     edx, 3
        mov     rsi, rsp
        mov     rdi, rsp
        call    rax
.L23:
        call    do_actual_work()
        mov     BYTE PTR [rbx+32], al
        add     rsp, 40
        pop     rbx
        pop     rbp
        ret
        mov     rbp, rax
        mov     rax, QWORD PTR [rsp+16]
        test    rax, rax
        je      .L25
        mov     edx, 3
        mov     rsi, rsp
        mov     rdi, 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。例如,当您编写共享库并且需要以通用方式获取用户提供的回调时,这是不可避免的。

【讨论】:

    猜你喜欢
    • 2016-12-18
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 2012-10-29
    • 1970-01-01
    • 1970-01-01
    • 2013-04-16
    • 2019-02-20
    相关资源
    最近更新 更多