とについて、とstd::unique_ptr
の違いは何ですか?p1
p2
std::move()
std::unique_ptr::reset()
p1 = std::move(p2);
p1.reset(p2.release());
とについて、とstd::unique_ptr
の違いは何ですか?p1
p2
std::move()
std::unique_ptr::reset()
p1 = std::move(p2);
p1.reset(p2.release());
答えは、[unique.ptr.single.assign]/2 の移動割り当ての標準仕様から明らかです。
効果:から を呼び出し、その後に からの代入を行ったかのように、 から
u
に所有権を譲渡します。*this
reset(u.release())
std::forward<D>(u.get_deleter())
明らかに移動の割り当ては、追加のことを行うため、 と同じではありませんreset(u.release())
。
追加の効果は重要です。これがないと、カスタム デリーターで未定義の動作が発生する可能性があります。
#include <cstdlib>
#include <memory>
struct deleter
{
bool use_free;
template<typename T>
void operator()(T* p) const
{
if (use_free)
{
p->~T();
std::free(p);
}
else
delete p;
}
};
int main()
{
std::unique_ptr<int, deleter> p1((int*)std::malloc(sizeof(int)), deleter{true});
std::unique_ptr<int, deleter> p2;
std::unique_ptr<int, deleter> p3;
p2 = std::move(p1); // OK
p3.reset(p2.release()); // UNDEFINED BEHAVIOUR!
}
たとえば、最初のものは、デストラクタの不一致がある場合に警告することができます。さらにrelease()
、非常に危険な機能であり、あなたの些細な例は正しいですが、他の多くの用途は正しくありません。この関数は絶対に使用しないことをお勧めします。
2 番目のバージョンは、例外セーフではない可能性があると思います。これは以下と同等です:
auto __tmp = p2.release();
p1.reset(__tmp);
したがって、std::unique_ptr::reset
スローへの呼び出しが発生した場合 (管理対象オブジェクトの削除がスローされた場合に発生する可能性があります)、参照されていないオブジェクトが存在し、破棄されることはありません。移動割り当ての場合、 は、の元のオブジェクトが適切に破棄さstd::unique_ptr
れるまで、実際の移動を待つことができます (またそうする必要があります) 。p1
ただし、これは管理対象オブジェクトのデストラクタがスローする可能性がある場合 (ほとんどの場合、それ自体が間違っています)、またはスローする可能性のあるカスタムのデリータを使用する場合にのみ問題になることに注意してください。したがって、実際には、通常、2 つのコード スニペット間に動作上の違いはありません。
編集:最後に、Jonathanは彼のコメントで、スローしないように標準でカスタム デリーターが必要であることを指摘していstd::unique_ptr::reset
ます。しかし彼は、別の違いがあることも指摘しています。それは、移動の割り当てだけがカスタムの削除機能も移動するという点です。これについては、彼も回答を書いています。
しかし、実際に結果として生じる振る舞いを無視すると、両者の間には大きな概念上の違いがあります。移動割り当てが適切な場合は、移動割り当てを行い、他のコードでエミュレートしないようにしてください。実際、最初のコード スニペットを 1 対 1 で 2 番目のコード スニペットに置き換える理由は思い浮かびません。DeadMGstd::unique_ptr::release
は、自分が何をしているのか、どのコンテキストで管理されていない動的オブジェクトをいじっているのかを本当に知っている場合にのみ使用する必要があるという点で正しいです。