使用新放置作为复制分配运算符不好吗?

Is using a placement new as a copy assignment operator bad?

本文关键字:运算符 分配 复制 新放      更新时间:2023-10-16

有时我想用const成员制作类/结构。我意识到这是一个坏主意,原因有很多,但为了论证,让我们假装唯一的原因是它使格式良好的operator =变得麻烦,至少可以说。但是,我设计了一个相当简单的解决方法,如以下结构所示:

struct S {
const int i;
S(int i) : i(i) {}
S(const S& other) : i(other.i) {}
S& operator =(const S& other) {
new (this) S(other);
return *this;
}
};

忽略析构函数和移动语义,有什么大理由不应该这样做吗?在我看来,它像一个更类型安全的版本

S& operator =(const S& other) {
const_cast<int&>(i) = other.i;
return *this;
}

因此,问题的总结是:不应该使用放置-new来实现复制分配以具有与复制构造相同的语义,这有什么主要原因吗?

我不认为放置新在这里是一个问题,而是产生未定义行为的const_cast

C++10.1.7.1-4除了可以修改任何声明为可变 (10.1.1) 的类成员之外,任何在其生存期 (6.6.3) 期间修改 const 对象的尝试都会导致未定义的行为。

在编译器开始优化之前,您可能会侥幸逃脱。

另一个问题是在活的(非销毁的)对象占用的片段内存上使用新的放置位置。但是你可能会侥幸逃脱,而有问题的对象有一个微不足道的析构函数。

有什么

真正重要的理由不应该这样做吗?

  1. 您必须绝对确定每个派生类都定义了自己的赋值运算符,即使它是微不足道的。因为派生类的隐式定义的复制赋值运算符会搞砸一切。它将调用S::operator=,这将在其位置重新创建错误类型的对象

  2. 这种销毁和构造赋值运算符不能由任何派生类重用。因此,您不仅强制派生类提供显式复制运算符,而且还强制它们在其赋值运算符中坚持相同的销毁和构造习惯用法。

  3. 您必须绝对确定在此类赋值运算符销毁和构造对象时没有其他线程正在访问该对象

  4. 类可能具有一些数据成员,这些成员不得受赋值运算符的影响。例如,线程安全类可能有某种互斥锁或关键节成员,当当前线程要销毁和构造该互斥锁时,其他一些线程正在等待它们......

  5. 在性能方面,它与标准的复制和交换习惯用法相比几乎没有任何优势。那么,经历上述所有痛苦会有什么收获呢?