2

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

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)

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


在这里使用placement new 操作符是否合理?或者这会对析构函数产生意想不到的后果?

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

3 回答 3

3

完成这项工作的最明显(也可能是最有效)的方法是提供复制分配和复制构造操作符来“做正确的事情”,按照这个一般顺序:

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对象而无需跳过箍以防止损坏。

于 2013-03-14T15:07:23.677 回答
1

它不会破坏 的旧值x,但你是对的,默认的复制赋值运算符也不会做你想做的事。

于 2013-03-14T14:53:54.747 回答
-1

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

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);
}

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

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

18.6.1.3 安置表格

...

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

返回:ptr。

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

(并且转换为void*再返回相同的指针类型的结果也保证与源指针相同)

但是,为避免不正确使用该类,定义复制赋值和复制构造函数或将此类声明为不可复制(删除的)会更安全

并且只有最后一个(不可复制的)情况可以被视为使用placement new 的原因。

尽管我远没有提倡将placement new 用于一般用途,但它表达了直接重用对象内存的意图,并且不依赖于任何优化。复制构造函数和复制赋值当然更安全,但不要准确表达这个意图:实际上不需要“复制”,应该构造新对象来代替旧对象。

于 2013-03-14T15:22:56.047 回答