这个问题是基于Scott Meyers最近发表的博客文章.
看起来"显而易见" std::swap(x, x)应该x在C++ 98和C++ 11中保持不变,但我无法在任何标准中找到任何保证.C++ 98 std::swap在复制构造和复制赋值方面进行了定义,而C++ 11则根据移动构造和移动赋值来定义它,这似乎是相关的,因为在C++ 11(和C++ 14)中,17.6 .4.9表示移动分配不需要自我分配安全:
如果函数参数绑定到右值引用参数,则实现可以假定此参数是对此参数的唯一引用.... [注意:如果程序在将左值传递给库函数时将左值转换为x值(例如通过使用参数move(x)调用函数),程序实际上要求该函数将该左值视为暂时的.实现可以自由地优化别名检查,如果参数是左值,则可能需要这些检查. - 尾注]
引起这种措辞的缺陷报告使结果清晰:
这澄清了移动赋值运算符不需要执行复制赋值运算符中常见(并且需要)的传统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);
}
Run Code Online (Sandbox Code Playgroud)
并且第一个赋值是对self进行赋值,其中参数是rvalue.如果移动赋值运算符T遵循标准库的策略并且不担心赋值给自己,那么这似乎可以判断未定义的行为,这也意味着它std::swap(x, x)也会具有UB.
即使是孤立的,这也是令人担忧的,但是如果我们认为std::swap(x, x)在C++ 98中应该是安全的,那么这也意味着C++ std::swap11/14可以默默地破坏C++ 98代码.
所以std::swap(x, x)保证x保持不变?在C++ 98中?在C++ 11中?如果是,那么它如何与17.6.4.9的移动分配许可相互作用而不是自我分配安全的?
我相信至少在 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交换时,右值或左值才是可交换的。T
那里没有关于自我交换的胡言乱语,它清楚地表明了任何右值或左值(分别),包括它自己。
(a)这两个约束也存在于 C++17、c++14 和 C++11 中,任何比这更旧的东西,我并不关心:-)