为什么移动std :: optional不重置状态

时间:2018-08-12 01:42:11

标签: c++ c++17 optional

我很惊讶地发现std::optional的move构造函数(以及相应的赋值)没有重置可选的move from,如在[19.6.3.1/7]中可以看到,该声明指出“ bool (rhs)保持不变。”

这也可以通过以下代码看到:

#include <ios>
#include <iostream>
#include <optional>
#include <utility>

int main() {
  std::optional<int> foo{ 0 };
  std::optional<int> bar{ std::move(foo) };

  std::cout << std::boolalpha
            << foo.has_value() << '\n'  // true
            << bar.has_value() << '\n'; // true
}

这似乎与在标准库中移动的其他实例(例如与std::vector)相抵触,在该实例中,从移出的容器通常以某种方式(在矢量的情况下,保证之后会为空)重置为“无效”即使包含在其中的对象本身已被移走。该决定是否应该支持这种或潜在的用例,例如试图模仿相同类型的非可选版本的行为?

4 个答案:

答案 0 :(得分:30)

除非另有说明,否则将类类型的移出对象保留为有效但未指定状态。不一定是“重置状态”,也绝对不是“无效”。

对于基本类型,移动与复制相同,即源不变。

具有原始成员的类类型的默认move-constructor将移动每个成员,即,保持原始成员不变;用户定义的move构造函数可能会或可能不会“重置”它们。

从中移出的向量中可能也可能没有元素。我们希望这样做不会,因为这样做效率很高,但是不能依靠它。

由于小字符串优化,从std::string移出的位置可能仍包含元素。


move上的

std::optional实际上是由标准(C ++ 17 [optional.ctor] / 7)指定的。它被定义为对包含的类型(如果存在)执行move。它不会将有价期权变成无价期权。

因此,实际上期望您的代码输出true true,并且foo中的实际包含值也应保持不变。


关于为什么 std::optional的move-constructor的定义是这样的:我不能肯定地说。但是optional不像最大大小为1的向量。它更像是带有有效性标志的变量。因此,移动optional就像移动变量是有道理的。

如果将optional移动到旧的“空”,则a = std::move(b);将调用b的托管对象的析构函数,这是意外的(至少对我来说是这样) )。

答案 1 :(得分:17)

总而言之:性能

首先,移动语义存在的主要原因之一是性能。因此,对于所有类型的特殊操作,移动构造和移动分配应尽可能快。

为了帮助实现此目标,通常的做法是将移出的对象保留为有效但未指定的状态。因此,optional移动构造/赋值所需要做的至少是从源参数移出。指定将源设置为在移动后不具有值,等于说:

  

移动后,做一些不必要的额外工作。

无论多余的工作有多小,它都不为零。一些(我敢说很多)客户不需要额外的工作,也不必为此付费。确实需要它的客户可以在移动之后轻松添加x.reset(),将移出的optional置于指定状态。

答案 2 :(得分:4)

paragraph所说的是,如果该可选变量具有值,则它仍然具有值。由于该值已从(移至)新移动的对象,因此该值可能与移动之前的值不同。这样,您就可以像从非可选对象上移动对象一样来访问从可选对象上移来的对象,因此,@inherits BlazorLayoutComponent <NavMenu /> <div class="body-content"> @Body </div> T(在包含对象时)的行为在被访问时移动之后是相同的。

此外,从可选动作进行移动的总体效果取决于所包含的类型optional<T>处理移动的方式。其他类(例如T)没有这种依赖性。

答案 3 :(得分:2)

虽然可以合理地预期std :: optonal的行为类似于std :: unique_ptr,该行为重置从对象移出的状态,但仍有理由不要求这种行为。我认为其中之一是普通类型的std :: optional应该是普通可复制类型。因此,它不能具有非默认的move构造函数,也不能重置has_value标志。

对于非平凡类型,具有std :: optional的行为与对平凡类型而言,具有可选行为的做法不同。