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:
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.
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
#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
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.
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 .
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With