此问题基于以下讨论recent blog post by Scott Meyers。
似乎“显而易见”的是std::swap(x, x)
应该在C ++ 98和C ++ 11中保持x
不变,但我无法在任何一种标准中找到任何保证。 C ++ 98在复制构造和复制赋值方面定义std::swap
,而C ++ 11在移动构造和移动赋值方面定义它,这似乎是相关的,因为在C ++ 11(和C +)中+ 14),17.6.4.9表示移动分配不需要是自我分配安全的:
如果函数参数绑定到右值引用参数,则实现可以假定此参数是对此参数的唯一引用。 ... [注意:如果程序将左值转换为 将左值传递给库函数时的xvalue(例如通过使用参数调用函数) move(x)),程序有效地要求该函数将该左值作为临时值处理。该 实现可以自由地优化掉参数检查,如果参数是可能需要的 一个左值。 - 后注]
产生这种措辞的defect report使结果清晰:
这澄清了移动赋值运算符不需要执行复制赋值运算符中常见(并且需要)的传统if(this!=& rhs)测试。
但是在C ++ 11和C ++ 14中,std::swap
应该使用这个实现,
template<typename T>
void swap(T& lhs, T& rhs)
{
auto temp(std::move(lhs));
lhs = std::move(rhs);
rhs = std::move(temp);
}
并且第一个赋值是对self进行赋值,其中参数是rvalue。如果T
的移动赋值运算符遵循标准库的策略并且不担心赋值给自己,那么这似乎表示未定义的行为,这意味着std::swap(x, x)
将具有UB,同样。
即使是孤立的,这也是令人担忧的,但是如果我们假设std::swap(x, x)
在C ++ 98中应该是安全的,那么这也意味着C ++ 11/14的std::swap
可以默默地打破C + +98代码。
std::swap(x, x)
保证x
保持不变吗?在C ++ 98中?在C ++ 11中?如果是,那么它如何与17.6.4.9的移动分配权限相互作用而不是自我分配安全的?
答案 0 :(得分:3)
您对C ++ 11/14 std::swap
实现的假设是错误的。在您的版本temp
中,正在从其中一个参数构造复制;但是,没有必要这样做。 temp
可以移动构建,然后其余部分与示例相同
template<typename T>
void swap(T& lhs, T& rhs) // omitting noexcept specification
{
T temp = std::move(lhs);
lhs = std::move(rhs);
rhs = std::move(temp);
}
第二个语句仍然是一个自动移动分配,但lhs
(和rhs
)拥有的任何资源都已移至temp
。因此,这两个对象现在应该处于&#34;有效但未指定的&#34; 状态,此时分配不会造成任何麻烦。
编辑:刚从Howard Hinnant找到this answer,他在那里讨论了这种情况(约有2/3的路程)。他的评估与上述相同。
答案 1 :(得分:2)
我相信至少在 C++20(a) 中,[utility.requirements]
部分可能会涵盖这一点,该部分指出:
15.5.3.2
描述了对可交换类型和可交换表达式的要求。
引用部分 [swappable.requirements]
进一步说明(我在最后两个要点中强调):
对象 t
可与对象 u
交换当且仅当:
swap(t, u)
和 swap(u, t)
在下面描述的上下文中计算时有效;和t
所引用的对象具有最初由 u
持有的值;和u
引用的对象具有 t
最初持有的值。在我看来,如果自交换以某种方式损坏了内容,那些粗体部分将无效,这意味着它们不会可交换。
同一部分后来还指出:
<块引用>一个右值或左值 t
是可交换的当且仅当 t
可分别与 any 右值或左值交换,类型为 { {1}}。
那里没有关于自交换的胡说八道,它清楚地声明任何右值或左值(分别),包括它自己。
(a) 这两个约束也存在于 C++17、c++14 和 C++11 中,任何比这更旧的东西,我真的不在乎 :-)