6

現在の実装

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

使用事例

ResourceManagerここでの使用例は、スタック割り当て変数または非ポインター クラス メンバーとして保持しながら、新しい値をポインターに再割り当てしたい場合です。

私の現在のセットアップでは、次のように使用することを想像しています。

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

// Use r...

// Destroy old ResourceManager and create the new one in place.
r = ResourceManager(&another_a);

これが問題でさえある理由は、B と C が代入できないという事実によるものです (たとえば、ファイル ストリームの場合)。

醜いオルタナティブ

別の醜い(そして危険な)方法はresetunique_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;    
}

間違った実装は、単にデフォルトの代入演算子を に使用することになることに注意してくださいResourceManagerこれにより、フィールドが順番に割り当てられます。これは、s の順番どおりの破棄を意味しunique_ptrますが、逆順の破棄が必要です。

質問

この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;
}
4

1 に答える 1

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

注: 移動割り当てが返されると、thatwill は「空」になり、 と の両方がthat.b_ptrになりthat.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;
 }  
于 2014-04-29T16:52:17.970 に答える