提问者:小点点

如何在不调用析构函数的情况下将值移出std:optional?


我正在尝试编写一个函数make_foo,它将“解包装”std::optional,返回包含的值。该函数假设optional已被使用,因此不对optional执行任何运行时检查。

下面是我对此的实现,以及编译后的程序集以供参考。关于编译器输出,我有几个问题:

>

  • 为什么这会导致分支代码?optional::operator*允许对包含的值进行未经检查的访问,因此我不希望看到任何分支。

    为什么调用foo的析构函数?请注意程序集中对on_destroy()的调用。如何在不调用析构函数的情况下将包含的值移出可选值?

    Godbolt连杆

    #include <optional>
    
    extern void on_destroy();
    
    class foo {
      public:
        ~foo() { on_destroy(); }
    };
    
    extern std::optional< foo > foo_factory();
    
    // Pre-condition: Call to foo_factory() will not return nullopt
    foo make_foo() {
        return *foo_factory();
    }
    
    
    make_foo():                           # @make_foo()
            push    rbx
            sub     rsp, 16
            mov     rbx, rdi
            lea     rdi, [rsp + 8]
            call    foo_factory()
            cmp     byte ptr [rsp + 9], 0
            je      .LBB0_2
            mov     byte ptr [rsp + 9], 0
            call    on_destroy()
    .LBB0_2:
            mov     rax, rbx
            add     rsp, 16
            pop     rbx
            ret
    

  • 共2个答案

    匿名用户

    你的方法或多或少是这样的:

    foo make_foo() {
       auto x = foo_factory(); 
       return *x;
    }
    

    其中x是从工厂返回的option(在代码中未命名为临时)。当x被销毁时,它要么调用所包含对象的析构函数(当有一个时)。或者它不破坏包含的对象(当没有对象时)。简而言之:您移出的foo仍然需要销毁,即使您知道可选内容包含它,编译器也不能销毁,因此产生了分支。

    如何在不调用析构函数的情况下将值移出std:optional?

    你不能。即使是一个移动的物体,最终也需要被销毁。

    匿名用户

    如何在不调用析构函数的情况下将值移出std:optional?

    就像你在例子中所做的那样。移动不调用析构函数。foo的析构函数由std::optional的析构函数调用,该析构函数通过销毁您创建的临时std::optional对象调用。

    您只能通过泄漏对象,或者首先避免对象的创建(从而也避免对象的移动)来防止对象被破坏。

    为什么这会导致分支代码?

    std::optional的析构函数中有一个分支。仅当std::optional不为空时,才调用包含对象的析构函数。

    optional::operator*允许对包含的值进行未经检查的访问,因此我不希望看到任何分支。

    理论上,如果优化器足够聪明,它可能会使用这些知识无条件调用析构函数,因为如果函数返回空的std::optional,它可能知道程序的行为是未定义的。做这样的优化似乎不够聪明。