1

这是一个学术问题。该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;

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

是否有任何可能的情况value() &&使std::move和有所不同value()

4

4 回答 4

4

std::move(optA).value()和之间没有区别std::move(optA.value())value()只返回一个引用包含值的glvalue,您可以value()返回一个lvalue,然后将其转换为xvalue std::move,或者您可以std::move先调用并value()立即给您一个xvalue(实际上,从lvalue到xvalue的转换将发生在value()方法本身的某个地方)。ref-qualified 重载在这种简单的情况下显然不是很有用,但是当可选项通过转发引用传递O&& o并且您想要std::forward<O>(o).value()“做正确的事情”时会很有用。

于 2019-06-19T16:30:24.547 回答
3
f(std::move(optA).value()); // OPTION 1

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

在这种情况下T && value() &&确实被称为。

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

T && value() &&这里没有调用,因为optAis not &&。所以你得到A&并将它转换为A&&by std::move,然后传递给f它大概什么都不做。optA没有改变,仍然报告它包含价值。

于 2019-06-19T16:41:09.677 回答
1

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

考虑以下:

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

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

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

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

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

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

void h(string s);

h(func2().s);

的参数hmove初始化。为什么?因为如果您访问纯右值的成员子对象,则结果表达式是一个 xvalue,因此可以在不显式使用的情况下进行移动std::move

&&构造函数optional确保其value工作方式相同。如果您在prvalue 临时调用它,那么它将返回一个xvalue,无需显式std::move调用即可将其移出。所以在最初的情况下,g的参数由 move 初始化,就像它访问prvalue的成员子对象时一样。

于 2019-06-19T19:11:11.390 回答
0

我会假设两个版本是相同的,但是,Nicol Bolas 的答案看起来很有趣。

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

  std::move(optA).value()

这表明您移动变量并依靠类的编写者来提供正确的重载。如果错过,您可能会错过。

一些 linter 也将其识别为不应再触及的变量,从而阻止您在移动后使用。

std::move(optA.value())

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

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

于 2019-06-19T19:39:01.010 回答