如何在不调用析构函数的情况下将值移出 std:optional?
How to move a value out of a std:optional without calling the destructor?
我正在尝试编写一个函数 make_foo
,它将“展开”一个 std::optional< foo >
,返回包含的值。
该函数假定可选已启用,因此不会对 optional
.
执行任何运行时检查
下面是我的实现,以及编译后的程序集供参考。
我有几个关于编译器输出的问题:
为什么这会导致分支代码? optional::operator*
允许对包含的值进行未经检查的访问,因此我不希望看到任何分支。
为什么要调用 foo
的析构函数?请注意程序集中对 on_destroy()
的调用。我们如何在不调用析构函数的情况下将包含的值移出可选值?
C++17 源代码
#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();
}
优化编译器输出 (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
你的方法大致是这样的:
foo make_foo() {
auto x = foo_factory();
return *x;
}
其中 x
是从工厂返回的 optional
(代码中未命名的临时值)。当 x
被销毁时,它要么调用包含对象的析构函数(当有一个时)。或者它不破坏包含的对象(当有 none 时)。简而言之:您从中移出的 foo
仍然需要销毁,即使您知道可选确实包含它,编译器也不能,因此分支。
How to move a value out of a std:optional without calling the destructor?
你不能。即使是移动的对象最终也需要销毁。
How to move a value out of a std:optional without calling the destructor?
就像您在示例中所做的那样。移动不调用析构函数。 foo
的析构函数由 std::optional
的析构函数调用,后者通过销毁您创建的临时 std::optional
对象调用。
您只能通过泄漏对象或首先避免创建(因此也避免移动)对象来防止对象被销毁。
Why does this result in branching code?
std::optional
的析构函数中有一个分支。只有当 std::optional
不为空时才会调用包含对象的析构函数。
optional::operator*
gives unchecked access to the contained value, so I would not expect to see any branching.
理论上,如果优化器足够聪明,它可能会利用这些知识无条件地调用析构函数,因为它可能知道如果函数返回空 std::optional
,程序的行为是未定义的。它似乎还不够聪明,无法进行这样的优化。
我正在尝试编写一个函数 make_foo
,它将“展开”一个 std::optional< foo >
,返回包含的值。
该函数假定可选已启用,因此不会对 optional
.
下面是我的实现,以及编译后的程序集供参考。 我有几个关于编译器输出的问题:
为什么这会导致分支代码?
optional::operator*
允许对包含的值进行未经检查的访问,因此我不希望看到任何分支。为什么要调用
foo
的析构函数?请注意程序集中对on_destroy()
的调用。我们如何在不调用析构函数的情况下将包含的值移出可选值?
C++17 源代码
#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();
}
优化编译器输出 (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
你的方法大致是这样的:
foo make_foo() {
auto x = foo_factory();
return *x;
}
其中 x
是从工厂返回的 optional
(代码中未命名的临时值)。当 x
被销毁时,它要么调用包含对象的析构函数(当有一个时)。或者它不破坏包含的对象(当有 none 时)。简而言之:您从中移出的 foo
仍然需要销毁,即使您知道可选确实包含它,编译器也不能,因此分支。
How to move a value out of a std:optional without calling the destructor?
你不能。即使是移动的对象最终也需要销毁。
How to move a value out of a std:optional without calling the destructor?
就像您在示例中所做的那样。移动不调用析构函数。 foo
的析构函数由 std::optional
的析构函数调用,后者通过销毁您创建的临时 std::optional
对象调用。
您只能通过泄漏对象或首先避免创建(因此也避免移动)对象来防止对象被销毁。
Why does this result in branching code?
std::optional
的析构函数中有一个分支。只有当 std::optional
不为空时才会调用包含对象的析构函数。
optional::operator*
gives unchecked access to the contained value, so I would not expect to see any branching.
理论上,如果优化器足够聪明,它可能会利用这些知识无条件地调用析构函数,因为它可能知道如果函数返回空 std::optional
,程序的行为是未定义的。它似乎还不够聪明,无法进行这样的优化。