【问题标题】:Implementing move assignment in terms of destructor and move constructor根据析构函数和移动构造函数实现移动赋值
【发布时间】:2015-03-03 10:03:11
【问题描述】:

假设我有一个管理内存的类,因此需要用户定义的特殊成员函数(想象一下vector 或类似的)。

考虑以下移动赋值运算符的实现:

Class& operator=(Class&& rhs)
{
    this->~Class();                    // call destructor
    new (this) Class(std::move(rhs));  // call move constructor in-place
}
  1. 以这种方式实现移动赋值运算符是否有效?也就是说,以这种方式调用析构函数和构造函数不会违反语言中的任何对象生命周期规则吗?

  2. 以这种方式实现移动赋值运算符是否是一个好主意?如果没有,为什么不呢,有没有更好的规范方法?

【问题讨论】:

  • 如果~Classnew (this) Class(std::move(rhs)) 抛出会发生什么?

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


【解决方案1】:

这是无效的:如果这个移动赋值被调用作为移动子对象的一部分呢?然后销毁子对象(假设它有一个虚拟析构函数)并在其位置重新创建一个父对象。

我会说,即使在非虚拟上下文中,这仍然是一个坏主意,因为您不会经常看到语法,而且它可能会使代码更难被未来的维护者理解。

最好的方法是避免完全编写自己的移动构造函数(并使用默认值),让所有类成员自己负责移动。例如,依赖 unique_ptr 等。如果不按照交换(作为复制分配的复制和交换)来实现它似乎是一种易于理解的机制。

【讨论】:

    【解决方案2】:
    1. 它可能是有效的(1)。为了解决您关于 dtor/ctor 生命周期的具体问题,是的,这是有效的(2)。这就是矢量的原始实现方式。
    2. 这可能是个好主意(可能不是),但您可能不想要规范的方法。(3)

    (1) 在自走案中,走棋是否有效存在争议。 主张自移动安全是代码应该安全的立场(呃),我们当然希望自分配是安全的。此外,一些用户体验报告称,对于许多使用移动的算法,自移动是可能的并且检查起来很繁琐。

    反对自动移动安全的立场是,移动语义的全部意义在于节省时间,因此移动应该尽可能快。相对于搬家的成本,自搬支票可能会很昂贵。请注意,编译器永远不会生成自移动代码,因为自然(非强制转换)右值不能自移动。进行自移动的唯一方法是显式调用“std::move()”转换。这给 std::move() 的调用者带来了负担,要么验证不涉及自移动,要么说服自己不涉及。另请注意,创建一个用户定义的“std::move”等价物检查自移动然后什么也不做是微不足道的。如果您不支持自行移动,则可能需要记录下来。

    (2) 这不是模板,因此您可以知道表达式“new (this) Class(std::move(rhs));”是否可以扔。如果可以,那么不,这是无效的。

    (3) 这段代码可能会让维护者感到困惑,他们可能期待更传统的交换方法,但交换方法存在潜在的缺点。如果目标正在释放的资源需要尽快释放(例如互斥体),那么swap的缺点是资源被交换到移动源对象中。如果移动是调用“std::move()”的结果,则移动源对象可能不会立即被处理掉。 (由于这不是模板,您可以知道正在释放哪些资源。如果内存是唯一被释放的资源,那么这不是问题。)

    更好的方法可能是从析构函数中提取资源释放代码,从移动构造函数中提取资源移动代码,然后在此移动赋值运算符中调用这些(内联)例程。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-22
      • 1970-01-01
      • 2013-11-18
      • 2017-01-16
      • 2021-05-31
      • 2016-05-19
      • 1970-01-01
      • 2013-06-11
      相关资源
      最近更新 更多