Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
like image 940
jonnybolton Avatar asked Mar 22 '21 14:03

jonnybolton


People also ask

Does STD exit call destructors?

std::exit causes normal program termination to occur. Several cleanup steps are performed: The destructors of objects with thread local storage duration ... are guaranteed to be called.

Does STD optional allocate?

What's more, std::optional doesn't need to allocate any memory on the free store. std::optional is a part of C++ vocabulary types along with std::any , std::variant and std::string_view .

What does STD optional do?

C++17 introduced std::optional<T> which lets you augment the values of a type T with a bonus value known as std::nullopt which semantically represents the absence of a value. A std::optional which holds the value std::nullopt is known as empty.

What happens when destructor is not called?

It is automatically called when an object is destroyed, either because its scope of existence has finished (for example, if it was defined as a local object within a function and the function ends) or because it is an object dynamically assigned and it is released using the operator delete.


1 Answers

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.

like image 148
eerorika Avatar answered Nov 26 '22 11:11

eerorika