放置new以避免复制构造函数

Placement new to avoid copy constructor

本文关键字:复制 构造函数 new 放置      更新时间:2023-10-16

我有一个简单的类,它包含一个指向它自己成员之一的指针:

struct X {
    int val;
    int* pVal;
    X(int v) : val(v), pVal(&val) {}
}
X x(1);

我有一些像这样的代码:

void foo() {
    doStuffWith(x);
    x = X(2); // completely discard the old value of X, and start again
    doStuffWith(x);
}

我担心当x被重新赋值时,如果没有进行返回值优化,x.pVal将无效地指向临时X(2)的成员。

我意识到我可以写一个复制构造函数来解决这个问题。然而,比起在内存中正确的位置开始构造对象,首先执行复制似乎是浪费的。


在这里使用new操作符是否合理?或者这会给析构函数带来意想不到的后果吗?

void foo() {
    doStuffWith(x);
    new (&x) X(2); // completely discard the old value of X, and start again
    doStuffWith(x);
}

实现此功能的最明显(可能也是最有效)的方法是提供复制赋值和复制构造操作符来"做正确的事情",大致顺序如下:

struct X {
    int val;
    int* pVal;
    X(int v) : val(v), pVal(&val) {}   
    X(X const &other) : val(other.val), pVal(&val) {} 
    // pVal was already set by ctor, so just ignore it:
    X &operator=(X const &other) { val = other.val; return *this; }
    // and allow assignment directly from an int:
    X &operator=(int n) { val = n; return *this; }
};

然后其余的代码可以只是复制/分配X对象,而不跳过环,以防止损坏。

它不会破坏x的旧值,但是您是对的,默认的复制赋值操作符也不会做您想要的。

对于我来说,如果X的析构函数在放置new之前被调用,这似乎是可以接受的解决方案。即使实际上没有为类指定析构函数,在语法上也是允许的。

struct X {
  int val;
  int* pVal;
  X(int v) : val(v), pVal(&val) {}
};
X x(1);
void foo() {
  doStuffWith(x);
  x.~X();
  new (&x) X(2); 
  doStuffWith(x);
}

在这种形式下,为任何对象重用内存都是正确的方法(但前提是对象的函数不能抛出!)否则在程序关闭时可能发生UB,即析构函数的双重调用)。

实际上,标准保证了从new位置传递和返回的指针在非数组形式中的相等性:

18.6.1.3放置表格

void* operator new(std::size_t, void* ptr) noexcept;

返回:ptr。

备注:故意不执行其他操作。

(以及转换为void*然后返回的结果)指针类型也保证与源指针相同)

然而,为了避免类的不当使用,定义复制赋值和复制构造函数或将该类声明为不可复制的(带有已删除的)

会更安全。

并且只有最后一个(不可复制的)情况才可以作为使用位置new的理由。

虽然我并不提倡将new放置用于一般用途,但它表达了直接重用对象内存的意图,并且不依赖于任何优化。复制构造函数和复制赋值当然更安全,但并不能完全表达这一意图:在实际需要时,应该构造新对象来代替旧对象。