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,我的输出是 1
、0
还是未指定?根据我的测试,has_value() 在这两种情况下都是正确的。
是否存在 value() &&
与 std::move
和 value()
不同的情况?
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());
f
的opt
参数会被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,应该无关紧要)
这是一个学术问题。 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,我的输出是 1
、0
还是未指定?根据我的测试,has_value() 在这两种情况下都是正确的。
是否存在 value() &&
与 std::move
和 value()
不同的情况?
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());
f
的opt
参数会被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,应该无关紧要)