以下是std::swap
的可能定义:
template<class T>
void swap(T& a, T& b) {
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
我相信
std::swap(v,v)
保证无效,std::swap
可按上述方式实施。以下引言似乎暗示这些信念是矛盾的。
17.6.4.9函数参数[res.on.arguments]
1以下各项适用于定义的函数的所有参数 除非另有明确说明,否则在C ++标准库中。
...
- 如果函数参数绑定到右值引用参数,则实现可以假定此参数是对其的唯一引用 这个论点。 [注意:如果参数是通用参数 形式T&amp;&amp;并且绑定了一个类型A的左值,该参数绑定到一个 左值参考(14.8.2.1)因此不在前面 句子。 - 结束注释] [注意:如果程序将左值转换为 将左值传递给库函数时的xvalue(例如通过 用参数move(x)调用函数,程序是 有效地要求该函数将该左值作为临时值处理。 实现可以自由地优化别名检查 如果参数是左值,则可能需要。 -endnote]
(感谢Howard Hinnant的providing the quote)
让v
成为从标准模板库中获取的某种可移动类型的对象,并考虑调用std::swap(v, v)
。在上面的a = std::move(b);
行中,T::operator=(T&& t)
内的this == &b
就是这种情况,因此参数不是唯一引用。这违反了上述要求,因此从a = std::move(b)
调用时,行std::swap(v, v)
会调用未定义的行为。
这里有什么解释?
答案 0 :(得分:6)
[res.on.arguments]是关于客户端应如何使用std :: lib的声明。当客户端向std :: lib函数发送xvalue时,客户端必须愿意假装xvalue确实是prvalue,并期望std :: lib利用它。
但是当客户端调用std :: swap(x,x)时,客户端没有向std :: lib函数发送xvalue。这是实现,而是这样做。因此,实现的责任在于使std :: swap(x,x)工作。
话虽如此,std已给予实现者一个保证:X应满足MoveAssignable
。即使处于移动状态,客户端也必须确保X是MoveAssignable。此外,std::swap
的实现并不真正关心自移动赋值的作用,只要它不是X的未定义行为即可。只要它没有崩溃。
a = std::move(b);
当&amp; a ==&amp; b时,此分配的源和目标都具有未指定(移动)值。这可以是无操作,也可以做其他事情。只要它不崩溃,std :: swap就能正常工作。这是因为在下一行:
b = std::move(tmp);
从前一行a
进入的任何值都将从tmp
获得一个新值。 tmp
的原始值为a
。因此,除了烧掉大量的cpu周期外,swap(a, a)
还是一个无操作。
<强>更新强>
latest working draft, N4618已被修改,以明确说明MoveAssignable
要求中的表达式:
t = rv
(其中rv
是右值),如果t
和rv
没有t
,则rv
只需要在分配之前为rv
的等效值引用相同的对象。无论如何,rv
的状态在分配后未指定。还有一个说明需要进一步澄清:
无论t
和rv
是否引用相同的对象,{{1}}仍必须满足使用它的库组件的要求。
答案 1 :(得分:3)
然后表达式a = std::move(b);
被执行,对象已经为空,处于一个只有很好定义破坏的状态。这实际上是无操作,因为左侧和右侧的物体已经是空的。移动后物体的状态仍然是未知但可破坏的。下一个语句将内容从tmp
移回,并将对象设置回已知状态。
答案 2 :(得分:2)
我同意你的分析,实际上libstdc ++调试模式有一个断言,它将触发标准容器的自交换:
#include <vector>
#include <utility>
struct S {
std::vector<int> v;
};
int main()
{
S s;
std::swap(s, s);
}
需要包装器类型S
,因为交换向量直接使用调用vector::swap()
的专门化,因此不使用通用std::swap
,但S
将使用泛型的,当编译为C ++ 11时,将导致向量成员的自动移动赋值,该成员将中止:
/home/toor/gcc/4.8.2/include/c++/4.8.2/debug/vector:159:error: PST.
Objects involved in the operation:
sequence "this" @ 0x0x7fffe8fecc00 {
type = NSt7__debug6vectorIiSaIiEEE;
}
Aborted (core dumped)
(我不知道&#34; PST&#34;应该是什么意思!我认为我测试的安装有问题。)
我相信GCC在这里的行为是符合的,因为标准说实现可以假设自动分配永远不会发生,因此断言永远不会在有效的程序中失败。
但是,我同意Howard的说法,这需要工作(并且可以毫无困难地工作 - 对于libstdc ++,我们只需要删除调试模式断言!),所以我们需要修改标准来制作自我移动或至少自我交换的例外。我一直承诺在一段时间内写一篇关于这个问题的论文,但还没有这样做。
我相信,既然在这里写答案,霍华德现在同意标准中目前的措辞存在问题,我们需要修复它以禁止libstdc ++使该断言失败。
答案 3 :(得分:0)
我认为这不是std::swap
的有效定义,因为std::swap
被定义为采用左值引用,而不是右值引用(20.2.2 [utility.swap])
答案 4 :(得分:0)
我的理解是直到最近才考虑这个问题,所以 C++20 标准中的现有措辞并没有真正解决它。
C++23 工作草案 N4885 在 [lib.types.movedfrom]/2
处包含 Library Working Group issue 2839 resolution:
在 C++ 标准库中定义的类型的对象可以移动分配 (11.4.6 [class.copy.assign]) 给自身。除非另有说明,否则这种赋值会将对象置于有效但未指定的状态。
这使得 std::swap
对标准库类型完全有效。