std::optional: simple 和 ref-qualified value() 之间的有效区别

std::optional: Effective difference between simple and ref-qualified value()

这是一个学术问题。 std::optional<T> 类型有一个 T && value() && 方法。我有以下定义:

class A { ... };
void f(A &&a);

以及以下程序:

std::optional<A> optA;
optA = A(1337);
f(std::move(optA).value()); // OPTION 1
f(std::move(optA.value())); // OPTION 2
std::cout << optA.has_value() << std::endl;

OPTION 1 和 OPTION 2 之间有什么有意义的区别吗?对于选项 1,我的输出是 10 还是未指定?根据我的测试,has_value() 在这两种情况下都是正确的。

是否存在 value() &&std::movevalue() 不同的情况?

std::move(optA).value()std::move(optA.value())没有区别。 value() 只是 return 一个引用包含值的 glvalue,或者你可以有 value() return 一个左值,然后通过 std::move 将其转换为 xvalue,或者你可以先调用 std::move 并让 value() 立即给你一个 xvalue(实际上,从左值到 xvalue 的转换将发生在 value() 方法本身的某个地方)。 ref-qualified 重载在这个简单的例子中显然不是很有用,但当可选参数通过转发引用 O&& o 传递并且您希望 std::forward<O>(o).value() 到 "do the right thing" 时,它会很有用。

f(std::move(optA).value()); // OPTION 1

你"moved"optA,但这并不意味着你改变了它。通常您不应该使用 "moved out" 之后的值,因为它处于有效但不确定的状态。 std::move 只是一个类型转换(与 static_cast<A&&>(optA) 完全相同)。未调用移动构造函数,因为没有创建 std::optional<A> 的新实例。因此 has_value returns true

在这种情况下 T && value() && 确实被调用了。

f(std::move(optA.value())); // OPTION 2
这里不调用

T && value() &&,因为optA不是&&。所以你得到 A& 并通过 std::move 将其转换为 A&&,然后传递给 f,这可能什么都不做。 optA 没有更改,仍然报告它包含值。

Is there any possible situation where value() && makes a difference over std::move and value()?

考虑以下几点:

optional<A> func() {...}

void f(optional<A> opt) {...}
void g(A a) {...}

f(func());
g(func().value());

fopt参数会被move初始化。从技术上讲,它将直接由纯右值初始化,但是 pre-C++17 意味着它得到 move-initialized。该初始化可以省略,但如果不是,则通过移动完成。总是。

但是g的参数呢?应该发生什么?好吧,考虑一下这会做什么:

struct C {string s;};
C func2() {...}

void h(string s);

h(func2().s);

h的参数由move初始化。为什么?因为如果您访问纯右值的成员子对象,则生成的表达式是一个 xvalue,因此无需显式使用 std::move.

即可进行移动

optional&& 构造函数确保 value 以相同的方式工作。如果你在一个纯右值临时调用它,那么它将 return 一个 xvalue,可以在没有显式 std::move 调用的情况下移动它。所以在最初的情况下,g 的参数是通过移动初始化的,就像它访问纯右值的成员子对象时一样。

我假设这两个版本是相同的,但是,尼可波拉斯的回答看起来很有趣。

假设它们都产生右值并且代码无关紧要,我们没有理由忽略可读性。

  std::move(optA).value()

这表明您移动变量并依赖 class 的作者提供正确的重载。错过了,你可能就错过了。

一些 linters 也将此识别为不应再触及的变量,从而阻止您 use-after-move。

std::move(optA.value())

然而,这会将 return 值转换为右值。我主要将此代码视为错误。函数 returns 按值(或 const 引用),我们写了很多代码。否则,函数 return 是一个左值,您可能会破坏变量的内部状态。

有了这个,我建议始终如一地移动变量并触发函数调用的移动作为代码味道。 (即使对于 std::optional,应该无关紧要)