std :: swap(x,x)是否保证x不变?

时间:2014-06-27 05:37:11

标签: c++ c++11 swap

此问题基于以下讨论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的移动分配权限相互作用而不是自我分配安全的?

2 个答案:

答案 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 中,任何比这更旧的东西,我真的不在乎 :-)