std::remove、std::move(范围)和移出的元素

std::remove, std::move(range) and moved-from elements

查看 std::remove 参考,我看到:

template< class ForwardIt, class T >
ForwardIt remove( ForwardIt first, ForwardIt last, const T& value );

删除是通过移动(通过移动赋值)范围内的元素来完成的。指向范围的新逻辑端和物理端之间元素的迭代器仍然是可解引用的,但元素本身具有未指定的值(根据 MoveAssignable post-条件) .

所以,我看到了 MoveAssignable 概念:

t = rv   // Post-condition:The new value of rv is unspecified.

到目前为止,没问题。关于 MoveAssignable 概念的问题:

std::string s1 = "abc";
std::string s2;
s2 = std::move(s1);
std::cout << s1;    // is this code valid?
s1 = "cde";         // is this code valid?

我可以通过读取它的值或重新分配它的值来重用 "moved from" 变量吗?

现在我正在查看 std::move 参考资料,它看起来有点令人惊讶:

template< class InputIt, class OutputIt >
OutputIt move( InputIt first, InputIt last, OutputIt d_first );

将范围 [first, last) 中的元素移动到从 d_first 开始的另一个范围。 此操作后,移出范围中的元素仍将包含适当类型的有效值,但不一定与移动前的值相同。

因此,std::remove 和 std::move 中移出元素的定义方式不同,尽管它们应该是相同的。哪一个是正确的?移出元素的重用(或不使用)规则是什么?

标准中的实际措辞可在 [lib.types.movedfrom] 部分找到:

Objects of types defined in the C++ standard library may be moved from ([class.copy]). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

std::move ([alg.move]) 的描述只是说元素被移动了,并没有对被移动的对象发表任何评论。 std::remove([alg.remove])的描述有一个不规范的注释:

Note: each element in the range [ret,last), where ret is the returned value, has a valid but unspecified state, because the algorithms can eliminate elements by moving from elements that were originally in that range.

所以 cppreference 恰好在这两个页面上使用了不同的措辞,但它们的意思完全相同。

现在,鉴于 std::string s1 已被移出,是否有效

std::cout << s1;

是的,它是允许的,因为 s1 是 "valid",但它没有用,因为您无法保证将输出什么。另一方面,

s1 = "cde";

既安全又有用,因为 s1 之前的所有内容都被丢弃了。

以"unless otherwise specified"为例,看一下std::unique_ptr<T>的移动赋值运算符([unique.ptr.single.asgn]):

unique_ptr& operator=(unique_ptr&& u) noexcept;

Effects: Transfers ownership from u to *this as if by calling reset(u.release()) followed by get_deleter() = std::forward<D>(u.get_deleter()).

由于 u.release() 离开 u empty/null,我们实际上保证移出的对象将是 empty/null。移动构造函数和模板化构造函数以及采用具有不同模板参数的 unique_ptr 的赋值也有类似的保证,因此从 unique_ptr 移出的 unique_ptr 始终是 empty/null.