在移动赋值和移动构造函数方面实现std :: swap

Bja*_*une 10 c++ stl move-semantics c++11

以下是可能的定义std::swap:

template<class T>
void swap(T& a, T& b) {
  T tmp(std::move(a));
  a = std::move(b);
  b = std::move(tmp);
}
Run Code Online (Sandbox Code Playgroud)

我相信

  1. std::swap(v,v) 保证没有效果
  2. std::swap 可以如上实现.

以下引用似乎暗示这些信念是矛盾的.

17.6.4.9函数参数[res.on.arguments]

1除非另有明确说明,否则以下各项均适用于C++标准库中定义的函数的所有参数.

...

  • 如果函数参数绑定到右值引用参数,则实现可以假定此参数是对此参数的唯一引用.[注意:如果参数是T &&形式的通用参数并且绑定了类型A的左值,则参数绑定到左值引用(14.8.2.1),因此不包含在前一句中. - 结束注释] [注意:如果程序在将左值传递给库函数时将左值转换为x值(例如通过使用参数move(x)调用函数),程序实际上会要求该函数处理该左值作为临时的.实现可以自由地优化别名检查,如果参数是左值,则可能需要这些检查.-endnote]

(感谢霍华德Hinnant(欣南特)用于提供报价)

让我们v从标准模板库中获取一些可移动类型的对象并考虑该调用std::swap(v, v).在该行a = std::move(b);以上,这是内部的情况T::operator=(T&& t)的是this == &b,这样的参数是唯一的参考.这违反了上面的要求,因此该行在a = std::move(b)调用时调用未定义的行为std::swap(v, v).

这里有什么解释?

How*_*ant 6

[res.on.arguments]是关于客户端应如何使用std :: lib的声明.当客户端向std :: lib函数发送xvalue时,客户端必须愿意假装xvalue确实是prvalue,并期望std :: lib利用它.

但是,当客户端调用std :: swap(x,x)时,客户端不会向std :: lib函数发送xvalue.这是实现,而是这样做.所以onus是在实现使std :: swap(x,x)工作.

话虽这么说,std给了实现者一个保证:X应该满足MoveAssignable.即使处于移动状态,客户端也必须确保X是MoveAssignable.此外,std::swap只要它不会崩溃,只要它不是XIe的未定义行为,实现并不真正关心自动分配的作用.

a = std::move(b);
Run Code Online (Sandbox Code Playgroud)

当&a ==&b时,此赋值的源和目标都具有未指定的(移动的)值.这可以是无操作,也可以做其他事情.只要它不崩溃,std :: swap就能正常工作.这是因为在下一行:

b = std::move(tmp);
Run Code Online (Sandbox Code Playgroud)

无论a前一行的价值是什么,都将获得一个新的价值tmp.并tmp具有原始价值a.所以除了烧掉很多cpu周期外,swap(a, a)还有一个无操作.

更新

最新的工作草案,N4618已被修改,明确规定,在MoveAssignable规定的表达式:

t = rv
Run Code Online (Sandbox Code Playgroud)

(其中rv是rvalue),t只需rv要是赋值之前的等价值,如果t并且rv不引用同一个对象.无论如何,rv在任命之后,状态是未指定的.还有一个说明需要进一步说明:

rv还必须满足在使用它,无论是否在该库组件的要求t,并rv指向同一个对象.

  • @HowardHinnant:你的第二条评论似乎是一个基本的逻辑错误.如果给出两个要求,则必须满足要求的逻辑结合(和).你不能"接受你的选择"关于满足哪种要求(逻辑分离).在这种情况下,对lhs(等价)有强烈的要求,对rhs的要求很弱(有效但未指定的状态).如果lhs和rhs相同,则需求变为:有效状态和等于其自己先前值的值.换句话说,`x = std :: move(x)`必须无效. (6认同)

Chr*_*odd 0

我认为这不是有效的定义,std::swap因为std::swap它被定义为采用左值引用,而不是右值引用(20.2.2 [utility.swap])