How to move a value out of a std:optional without calling the destructor?

Viewed 581

I am trying to write a function, make_foo, that will "unwrap" a std::optional< foo >, returning the contained value. The function assumes that the optional is engaged so does not perform any runtime checks on the optional.

My implementation of this is below, along with the compiled assembly for reference. I have a couple of questions about the compiler output:

  1. Why does this result in branching code? optional::operator* gives unchecked access to the contained value, so I would not expect to see any branching.

  2. Why does foo's destructor get called? Note the call to on_destroy() in the assembly. How do we move the contained value out of the optional without calling the destructor?

Godbolt link

C++17 source

#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();
}

Optimized compiler output (Clang 11)

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 Answers

Your method is more or less this:

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

Where x is the optional returned from the factory (unnamed temporary in your code). When x is destroyed, it either calls the destructor of the contained object (when there is one). Or it does not destroy the contained object (when there is none). In short: The foo you moved from still needs to be destroyed and even though you know that the optional does contain it, the compiler cannot, hence the branch.

How to move a value out of a std:optional without calling the destructor?

You can't. Even a moved from object needs to be destroyed eventually.

How to move a value out of a std:optional without calling the destructor?

Like you did in the example. The destructor is not called by the move. The destructor of foo is called by the destructor of the std::optional which is called by the destruction of the temporary std::optional object that you created.

You can only prevent an object from being destroyed by leaking it, or by avoiding creation (and thus also the move) of the object in the first place.

Why does this result in branching code?

There is a branch in the destructor of std::optional. The destructor of the contained object is called only if the std::optional is not empty.

optional::operator* gives unchecked access to the contained value, so I would not expect to see any branching.

In theory, if the optimiser was smart enough, it might use this knowledge to call the destructor unconditionally, since it might know that behaviour of the program is undefined if the function returned an empty std::optional. It seems to not have been smart enough to make such optimisation.

Related