在'this'指针上使用placement new是否安全

Wae*_*elJ 6 c++ raii placement-new explicit-destructor-call

目前的实施

我有一个包含unique_ptr相互依赖的字段的类:

class ResourceManager {
  ResourceManager() {}

  ResourceManager(A* a_ptr) :
    b_ptr(new B(a)),
    c_ptr(new C(b_ptr.get())) {}

  ResourceManager& operator=(ResourceManager&& that) {
    // Call destructor, then construct a new instance on top
    ~ResourceManager();
    ResourceManager* new_this = new(this) ResourceManager();

    // Surely this must be the case, right?
    // Is there any reason to prefer using either?
    assert(new_this == this);

    new_this->b_ptr = that.b_ptr;
    new_this->c_ptr = that.c_ptr;

    return *new_this;
  }

  unique_ptr<B> b;
  unique_ptr<C> c;
};
Run Code Online (Sandbox Code Playgroud)

用例

这里的用例是我想将新值重新分配给指针,同时保持ResourceManager作为堆栈分配的变量,或者作为非指针类成员.

使用我当前的设置,我想像使用这样的东西:

A a, another_a;
ResourceManager r(&a);

// Use r...

// Destroy old ResourceManager and create the new one in place.
r = ResourceManager(&another_a);
Run Code Online (Sandbox Code Playgroud)

这甚至是一个问题的原因是由于B和C是不可分配的(例如文件流)

丑陋的选择

另一种丑陋(和危险)的方法是以相反的顺序显式地reset显示unique_ptr字段(记住C依赖于B,因此必须首先被破坏),有效地模仿默认的破坏行为.

ResourceManager& operator=(ResourceManager&& that) {
  // Mimic destructor call (reverse-order destruction)
  c_ptr.reset();
  b_ptr.reset();

  b_ptr = that.b_ptr;
  c_ptr = that.c_ptr;

  return *this;    
}
Run Code Online (Sandbox Code Playgroud)

请注意,错误的实现只是使用默认赋值运算符ResourceManager.这将按顺序分配字段,这意味着unique_ptrs 的有序销毁,而我们需要逆序销毁.

问题

这是使用的this指针与安置new和明确的析构函数调用安全吗?

我必须使用返回的new_this指针而不是原始this指针(例如,如果this指针在调用析构函数后在技术上变得无效)?

有没有更好的建议方法来实现这一目标?如果unique_ptr在类中添加更多这样的字段,我必须确保将一个副本添加到赋值运算符.例如,是否可以调用移动构造函数,如下所示:

ResourceManager& operator=(ResourceManager&& that) {
  // Call destructor
  ~ResourceManager();

  // Move-construct a new instance on top
  ResourceManager* new_this = new(this) ResourceManager(that);
  return *new_this;
}
Run Code Online (Sandbox Code Playgroud)

Dal*_*son 4

您的解决方案似乎过于复杂。

我会这样编码:

class ResourceManager {
  ResourceManager() {}

  ResourceManager(A* a_ptr) :
    b_ptr(new B(a)),
    c_ptr(new C(b_ptr.get())) {}

  ResourceManager& operator=(ResourceManager&& that) 
  {
    // the order of these moves/assignments is important
    // The old value of *(this->c_ptr) will be destroyed before
    // the old value of *(this->b_ptr) which is good because *c_ptr presumably
    // has an unprotected pointer to *b_ptr.
    c_ptr = std::move(that.c_ptr);
    b_ptr = std::move(that.b_ptr);
    //  (a better solution might be to use shared_ptr<B> rather than unique_ptr<B> 
    return *this;
  }

  unique_ptr<B> b_ptr;
  unique_ptr<C> c_ptr;
};
Run Code Online (Sandbox Code Playgroud)

注意:当移动赋值返回时,thatwill “empty”,意味着that.b_ptrthat.c_ptr都是nullptr。这是移动分配的预期结果。

或者,如果“重建”赋值的目标很重要(假设本示例中未显示额外的代码使其如此),我可能会添加一个移动构造函数和一个交换方法,如下所示:

 ResourceManager(ResourceManager&& that)
 : b_ptr(std::move(that.b_ptr)),
   c_ptr(std::move(that.c_ptr))    
 {
 }

 void swap(ResourceManager & that)
 {
   b_ptr.swap(that.b_ptr);
   c_ptr.swap(that.c_ptr);  
 }

 ResourceManager& operator=(ResourceManager&& that) 
 {
     ResourceManager temp(std::move(that));
     this->swap(temp);
     return *this;
 }  
Run Code Online (Sandbox Code Playgroud)