【问题标题】:Are all the std::move()-based std::swap() implementations I'm seeing buggy? [duplicate]我看到的所有基于 std::move() 的 std::swap() 实现都有问题吗? [复制]
【发布时间】:2012-07-26 08:25:19
【问题描述】:

可能重复:
What can I do with a moved-from object?

例如,见this code:

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

只是我,还是这里有错误?如果你把movea变成tmp,那么a不就失效了吗?

即从ba 的移动分配不应该是一个带有位置new 的移动构造函数调用吗?
如果不是,那么移动构造函数和移动赋值运算符有什么区别?

template<class T>
void swap(T& a, T& b)
{
    T tmp(std::move(a));
    new(&a) T(std::move(b));
    new(&b) T(std::move(tmp));
}

【问题讨论】:

  • @FredOverflow:感谢您提供的链接,这些很有帮助。但我不明白:如果移动分配不需要需要一个有效的目标开始,那么移动构造的意义何在?那它们不就是一回事吗?
  • @Mehrdad:假设您有一个对象,其构造和复制操作成本很高。当您创建一个临时对象(例如,一个 r 值对象)时,您首先调用代价高昂的构造函数,然后调用代价高昂的复制构造函数。但是使用移动构造函数可以避免昂贵的复制,取而代之的是更轻量的复制操作(例如,将指针复制到已分配的数组,而不是分配新数组并复制其内容)。
  • @Mehrdad 你还是一头雾水。就像复制赋值一样,移动赋值确实需要一个有效的目标对象。从一个对象移动不会使它无效。被移动的对象仍然是一个对象,需要像任何其他对象一样被破坏。如果移出的对象因某种原因无效,几乎所有重要的析构函数都会在尝试销毁它时对系统造成严重破坏。

标签: c++ c++11 swap move-semantics move-constructor


【解决方案1】:

当您将数据移出对象时,预期的语义是被移出的对象最终处于未指定但有效状态。这意味着您无法预测对象将处于什么状态,除非它将是一个格式良好的对象。这与“这个对象已经死了并且消失了”不太一样。将数据移出对象并不会结束对象的生命周期——它只是将其状态更改为未指定的状态——因此为该对象分配一个新值是完全安全的。

因此,swap 函数的初始版本是安全的。从 a 移动数据后,该对象拥有一些未指定的“安全但不可预测”的值。然后在将 b 的值移动分配给它时,会覆盖该值。

第二个版本是不安全的,因为在你尝试在它之上构造一个新对象之前,a 的生命周期还没有结束。这会导致未定义的行为。

希望这会有所帮助!

【讨论】:

  • @Mehrdad:它的值是未指定。您不应该依赖它具有特定值,即使对象具有 empty 值是有意义的。
  • @Xeo 不确定你的意思,但是从一个对象移动肯定不会结束它的生命周期。生命周期在析构函数开始时结束,而当您从对象移动时不会发生这种情况。
  • @akappa 这是未定义的行为。 Xeo 是正确的对于这样做不是未定义行为的情况。标准参考:§3.8 第 4 段。
  • @Mehrdad:或者直到你从别人那里得到一个新的大脑、心脏等。
  • @akappa:他知道这一点,对于 POD 类型 IIRC 来说,这不是未定义的行为。
【解决方案2】:

a 仍然有效。 a 中的数据将不再可靠。 移动资源时不会释放 a,因此将新资源移动到 a 非常好。

【讨论】:

  • 那么移动构造和移动赋值有什么区别呢?
  • @Mehrdad 一个创建了一个新对象,另一个没有?
  • 呃……如果对象已经移出a的存储空间,那么a不是死了吗?如果a 没有“无效状态”的概念怎么办?
  • 移动的不是a,是a里面的资源。如果你在a 中有一个指向某个内存的指针,例如a-&gt;data,并且你将data 移动到另一个对象,a 仍然存在。
  • @Mehrdad:移动构造函数对于非 POD 类型具有未定义的行为;通常不允许您在现有对象之上构造对象。例如,a 尽管已被移动,可能仍然拥有一些资源,在这种情况下,您的代码会泄漏该资源。你可以通过首先调用析构函数来拼凑一些几乎有效的东西,但是赋值是给现有对象一个新值的方法。
【解决方案3】:

移动不会使对象无效。相反,它总体上仍处于有效但不确定的状态。特定类别有额外保证;例如,std::unique_ptr 保证移出后为空。

保留一个有效的对象特别意味着可以完全分配给该对象,这是原始代码所做的。

您自己提出的解决方案被严重破坏:当您在旧对象之上放置-构造一个新对象时,旧对象的生命周期就结束了。但是,如果类的析构函数有效果,那么省略调用析构函数是未定义的行为。

此外,如果您确实首先正确调用了析构函数,但随后在 构造函数 中遇到了异常,那么您将遇到麻烦,因为您现在没有需要调用的有效对象在范围出口处被销毁。这是关于此主题的related question of mine

【讨论】:

    猜你喜欢
    • 2019-07-09
    • 2018-10-18
    • 2020-10-19
    • 2021-10-01
    • 2016-09-10
    • 1970-01-01
    • 1970-01-01
    • 2015-03-09
    • 2017-05-28
    相关资源
    最近更新 更多