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

Lem*_*rop 20 c++ optional c++17

我很惊讶地得知移动构造函数(以及该事项的赋值)std::optional没有重置可选的移动,如[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
}
Run Code Online (Sandbox Code Playgroud)

这似乎与在标准库中移动的其他实例相矛盾,例如移动std::vector容器的位置通常以某种方式重置(在向量的情况下保证之后为空)以使其"无效",即使其中包含的对象也是如此他们自己已经离开了.这个或潜在的用例是否有任何理由支持,例如可能试图模仿相同类型的非可选版本的行为?

M.M*_*M.M 35

除非另行指定,否则类类型的移动对象将保持有效但未指定的状态.不一定是"重置状态",绝对不是"无效".

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

具有原始成员的类类型的默认移动构造函数将移动每个成员,即保持原始成员不变; 用户定义的移动构造函数可能会也可能不会"重置"它们.

移动的向量可能或可能不包含其中的元素.我们期望它不会,因为那是有效的,但它不能依赖.

std::string由于小字符串优化,移动的可能仍然包含元素.


moveon std::optional实际上由标准指定(C++ 17 [optional.ctor]/7).它被定义为move对包含的类型进行操作(如果存在).它不会将有价值的可选项转换为无值的可选项.

因此,实际上预计您的代码输出true true,实际包含的值也foo应该保持不变.


关于为什么 std::optional移动构造函数是这样定义的问题:我不能肯定地说; 但是a optional不像最大大小为1的向量.它更像是一个带有有效性标记的变量.因此,移动a optional就像移动变量一样有意义.

如果optional向左移动旧的"空",那么a = std::move(b);将调用b托管对象的析构函数,这将是意外的(至少对我来说).


How*_*ant 24

总之:性能.

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

为了帮助实现这一目标,标准做法是将对象移动到有效但未指定的状态.因此,optional移动构造/赋值所需要做的最小的事情就是从源参数移动.要指定在移动后将源设置为没有值,则相当于:

搬家后,做一些额外的,不必要的工作.

无论额外工作多么小,它都是非零的.一些(我敢说很多)客户不需要额外的工作,也不应该付钱.需要它的客户可以x.reset()在移动后轻松添加,将移动optional到一个明确指定的状态.

  • @NoSenseEtAl:我只能代表libc ++`std :: string`。确实确实使从“字符串”移出的内容为空,但这并不是因为它是“错误的”,而是因为这是该设计最快的方法。libc ++`string`实际上是从move成员向外设计的。它复制“字符串”对象的所有位,然后将源“字符串”对象的所有位清零。它不检查`string`是长还是短。无论字符串处于长模式还是短模式,此复制零归零算法都是正确的。在长模式下避免零是不正确的。 (2认同)
  • 霍华德(Howard)在libc ++中描述了“从字符串移开”的情况:/sf/ask/3688748941/。 (2认同)

120*_*arm 5

段的意思是,如果该可选项具有值,那么它仍然具有值。由于该值已从(移至)新移动的对象,因此该值可能与移动之前的值不同。这使您可以访问移动,从可选的对象相同的方式,一个移动-从非可选的对象,所以行为Toptional<T>(当它包含一个对象)当移动后访问是一样的。

另外,从可选动作进行移动的总体效果取决于所包含的类型如何T处理移动。其他类(如vector)没有这种依赖性。