根据析构函数和移动构造函数实现移动赋值

Implementing move assignment in terms of destructor and move constructor

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

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

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

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

无效:如果此移动分配作为移动子对象的一部分被调用怎么办?然后你销毁子对象(假设它有一个虚拟析构函数)并在它的位置重新创建一个父对象。

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

最好的方法是让所有 class 成员自行移动,从而避免完全编写自己的移动构造函数(并使用默认构造函数)。例如依赖 unique_ptr 等。失败的是,似乎在交换方面实现它(作为复制分配的复制和交换)将是一种易于理解的机制。

  1. 可能有效(1)。要解决有关 dtor/ctor 生命周期的具体问题,是的,这是有效的 (2)。这就是 vector 的原始实现方式。
  2. 这可能是个好主意(也可能不是),但您可能不想要规范的方式。(3)

(1) 或自走棋是否需要走棋有效,存在争议。 主张自我移动安全是代码应该安全的立场(duh),我们当然希望自我分配是安全的。还有一些用户体验报告说,对于许多使用 move 的算法,self-move 是可能的并且检查起来很乏味。

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

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

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

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