这是一个学术问题。 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之间有有意义的区别吗?对于选项1,我是否有1
,0
或未指定为输出?根据我的测试,在两种情况下has_value()仍然为真。
是否有可能value() &&
与std::move
和value()
有所不同?
答案 0 :(得分:4)
std::move(optA).value()
和std::move(optA.value())
之间没有区别。 value()
仅返回引用包含值的glvalue,或者您可以让value()
返回一个左值,然后通过std::move
将其转换为xvalue,或者可以调用{{1} }首先,让std::move
立即给您一个xvalue(实际上,从lvalue到xvalue的转换将发生在value()
方法本身内的某个位置)。 ref限定的重载在这种简单情况下显然不是很有用,但是当转发参考value()
传递了可选参数并且您希望O&& o
“做正确的事”时,有用。 / p>
答案 1 :(得分:3)
f(std::move(optA).value()); // OPTION 1
您“移动”了optA
,但这并不意味着您对其进行了更改。通常,在“移出”值后,您不应再使用它,因为它处于有效但不确定的状态。 std::move
只是类型转换(与static_cast<A&&>(optA)
完全相同)。由于没有创建std::optional<A>
的新实例,因此未调用移动构造函数。因此,has_value
返回true
。
在这种情况下,确实调用了T && value() &&
。
f(std::move(optA.value())); // OPTION 2
T && value() &&
在这里没有被调用,因为optA
不是&&
。因此,您得到A&
并由A&&
投射到std::move
,然后传递到f
,该行大概什么也不做。 optA
未被更改,仍然报告它包含值。
答案 2 :(得分:1)
是否有可能value()&&与std :: move和value()有所不同?
请考虑以下内容:
optional<A> func() {...}
void f(optional<A> opt) {...}
void g(A a) {...}
f(func());
g(func().value());
f
的{{1}}参数将通过移动来初始化。从技术上讲,它将直接由prvalue初始化,但是C ++ 17之前的版本意味着它将被移动初始化。该初始化可以省去,但是如果不是,则可以通过move完成。总是。
但是opt
的参数呢?应该怎么办?好吧,考虑一下该怎么做:
g
struct C {string s;};
C func2() {...}
void h(string s);
h(func2().s);
的参数由 move 初始化。为什么?因为如果您访问prvalue的成员子对象,则结果表达式是xvalue,因此有资格进行移动而无需显式使用h
。
std::move
的{{1}}构造函数可确保&&
以相同的方式工作。如果在prvalue临时上调用它,则它将返回一个xvalue,无需显式optional
调用即可将其移出。因此,在原始情况下,value
的参数是通过move初始化的,就像访问访问prvalue的成员子对象时一样。
答案 3 :(得分:0)
我认为两个版本是相同的,但是,尼科尔·波拉斯的答案看起来很有趣。
假设它们都导致右值且代码无关紧要,那么我们就没有理由忽略可读性。
std::move(optA).value()
这建议您移动变量,并依靠类的编写者提供正确的重载。如果丢失,您可能会错过。
某些短毛猫也将其视为不应该被触摸的变量,从而阻止您移动后使用。
std::move(optA.value())
但是这会将返回值转换为右值。我主要将此代码视为错误。函数要么按值(或const引用)返回,然后我们编写了很多代码。否则,该函数将返回一个左值,您可能会破坏该变量的内部状态。
因此,我建议一致地移动变量并以代码味道触发函数调用的移动。 (即使对于std :: optional,也无关紧要)