【问题标题】:Is move assignment via destruct+move construct safe?通过 destruct+move 构造的移动赋值安全吗?
【发布时间】:2012-10-17 00:47:12
【问题描述】:

这是为大多数具有移动构造函数的类定义移动赋值的一种非常简单的方法:

class Foo {
public:
  Foo(Foo&& foo);                     // you still have to write this one
  Foo& operator=(Foo&& foo) {
    if (this != &foo) {               // avoid destructing the only copy
      this->~Foo();                   // call your own destructor
      new (this) Foo(std::move(foo)); // call move constructor via placement new
    }
    return *this;
  }
  // ...
};

在标准 C++11 中,调用您自己的析构函数然后在 this 指针上放置 new 的顺序是否安全?

【问题讨论】:

  • 你的移动构造函数最好是noexcept,否则你会尝试摧毁一个已经被摧毁的物体,如果它在UB-land中抛出并徘徊。
  • 移动/复制分配的一个好技巧是简单地take the parameter by value。用户将移动构造 value 参数或复制构造它(或隐藏到其中)。然后,您可以使用 std::swap 将值交换到您的对象中。

标签: c++ c++11 move-semantics placement-new move-assignment-operator


【解决方案1】:

只有当你永远不会从这个类派生一个类型时。如果这样做,这会将对象变成怪物。不幸的是,该标准将此作为解释对象生命周期的示例。在现实世界的代码中这样做是一件非常糟糕的事情。

【讨论】:

  • 获取新分配的内存,手动〜dtoring它,然后放置newing它是否也有问题?我必须进行标准研究以检查自己是否允许这样做。
  • @Yakk 不,因为你没有 [de] 分配任何东西。
  • @Yakk 不,在这方面应该没有问题,只要您最初分配的对象的大小和对齐要求足以满足您通过放置 new 创建的对象以及您传递给 delete 的指针是合适的类型。
  • 如果派生类有虚析构函数或者需要为移动赋值做一些特殊的事情,移动赋值不应该是虚的并且在子类中也被覆盖吗?在这种情况下,派生类可以以相同的方式实现移动赋值。如果派生类作者不知道不要调用超类移动赋值,那么就有问题了,是的 - 要么一直这样,要么根本没有。这是您特别关心的问题吗?
  • @InnocentBystander — 添加一个虚函数,并在派生类中覆盖它。现在(这里有太多的实现细节)该赋值运算符中的构造函数调用将用基类型的 vtable 指针替换派生类型的 vtable 指针。瞧,派生类型的对象,但虚拟调用不会转到它们的覆盖器。
【解决方案2】:

从技术上讲,源代码在这个小例子中是安全的。但现实情况是,如果你甚至看到 Foo 很有趣,你就会调用 UB。它非常不安全,完全不值得。像其他人一样使用交换——这是有原因的,因为那是正确的选择。此外,自我分配检查也很糟糕。

【讨论】:

  • 复制和交换并不总是最好的实现。这是获得强大的异常安全保证的一种简单方法,但需要权衡取舍。 Howard Hinnant 在this 回答中讨论了它。
  • 您主要关心的是派生类作者(在这种情况下是错误的)调用 Foo 移动分配,还是有更多问题?这种技术确实需要子类作者虚拟地覆盖并使用该技术。我能想到的大多数问题都涉及到移动分配不是虚拟的,然后答案就是在这些情况下移动分配应该是虚拟的,无论是否使用这种技术(你同意吗?)。除此之外,您还有其他顾虑吗?
猜你喜欢
  • 2020-12-30
  • 2013-06-11
  • 2020-03-22
  • 1970-01-01
  • 1970-01-01
  • 2014-02-16
  • 2016-05-19
  • 2020-12-03
  • 2016-09-13
相关资源
最近更新 更多