現在の実装
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 が代入できないという事実によるものです (たとえば、ファイル ストリームの場合)。
醜いオルタナティブ
別の醜い(そして危険な)方法は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;
}
間違った実装は、単にデフォルトの代入演算子を に使用することになることに注意してください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;
}