web-dev-qa-db-ja.com

shared_ptr.resetまたはoperator =を使用する方が良いですか?

私はC++ 11の新しいイディオムに頭を悩ませようとしています。

少なくともshared_ptrでは、new T()make_shared<T>()の使用には実質的な違いがあるようです。

しかし、何かの新しいインスタンスを指すように共有ポインタをリセットするのはどうでしょうか。以前は、通常reset(new T())メンバーを使用していました。しかし、これはそもそもmake_shared()を使用しないのと同じ問題に悩まされていませんか? (つまり、make_sharedがオブジェクトを割り当てることを許可しないため、参照カウントをT自体と同じ割り当てではなく、別の割り当てに配置する必要がありますか?)

使用する方が単純に良いですか?

   mysharedptr = make_shared<T>(args...);

それとももっと良い方法はありますか?

そして、make_sharedのように引数のオファーの可変個引数転送をリセットして、mysharedptr.reset(args ...);を記述できるようにするべきではありませんか?

34
Mordachai

実際、次の間に実質的な違いがあります。

_shared_ptr<T> sp(new T());
_

そして:

_shared_ptr<T> sp = make_shared<T>();
_

最初のバージョンは、Tオブジェクトの割り当てを実行してから、別の割り当てを実行して参照カウンターを作成します。 2番目のバージョンは、オブジェクトと参照カウンターの両方に対して単一の割り当てを実行し、それらをメモリの連続した領域に配置するため、メモリのオーバーヘッドが少なくなります。

また、一部の実装では、_make_shared<>_の場合にさらにスペースの最適化を実行できます(MSの実装によって実行される「WeKnow Where YouLive」の最適化を参照してください)。

ただし、_make_shared<>_が存在する理由はそれだけではありません。明示的なnew T()に基づくバージョンは、状況によっては例外安全ではありません。特に、_shared_ptr_を受け入れる関数を呼び出す場合はそうです。

_void f(shared_ptr<T> sp1, shared_ptr<T> sp2);

...

f(shared_ptr<T>(new T()), shared_ptr<T>(new T()))
_

ここで、コンパイラは最初のnew T()式を評価し、次に2番目のnew T()式を評価し、対応する_shared_ptr<>_オブジェクトを作成できます。しかし、最初に割り当てられたオブジェクトがその_shared_ptr<>_にバインドされる前に、2番目の割り当てによって例外が発生した場合はどうなるでしょうか。漏れます。 make_shared<>()では、これは不可能です。

_f(make_shared<T>(), make_shared<T>())
_

割り当てられたオブジェクトは、make_shared<>()への各関数呼び出し内のそれぞれの_shared_ptr<>_オブジェクトにバインドされるため、この呼び出しは例外安全です。これは、自分が何をしているのかを本当に理解していない限り、裸のnewを使用してはならないもう1つの理由です。 (*)

reset()についてのあなたの意見を考慮すると、新しい_shared_ptr<>_の構築が実行するのと同じように、reset(new T())がカウンターとオブジェクトに対して別々の割り当てを実行することを観察するのは正しいことです。生のポインタが引数として渡される場合の個別の割り当て。したがって、_make_shared<>_を使用した割り当て(またはreset(make_shared<T>())などのステートメント)が推奨されます。

reset()が可変個引数リストをサポートする必要があるかどうかにかかわらず、これはおそらく、StackOverflowが適切ではない一種のオープンディスカッションです。

(*)まだそれを必要とするいくつかの状況があります。たとえば、C++標準ライブラリには_make_unique<>_に対応する_unique_ptr_関数がないため、自分で作成する必要があります。もう1つの状況は、オブジェクトとカウンターを単一のメモリブロックに割り当てたくない場合です。これは、オブジェクトへの弱いポインタが存在すると、全体ブロックの割り当てが解除されないためです。オブジェクトへのより多くの所有ポインタが存在します。

36
Andy Prowl

正解です。reset(new T...)shared_ptr(new T...)のすべての問題を抱えています。二重割り当てが発生し、例外安全性もありません(_bad_alloc_がreset内で発生しない限り、リークの可能性はほとんどありません)。

resetshared_ptr<T>(ptr).swap(*this)と同等であると文書化されているため、次のように書くこともできます。

_make_shared<T>(args...).swap(mysharedptr);
_

_make_shared<T>_からの割り当てはほぼ同等ですが、唯一の違いは、古いTの削除と一時的な_shared_ptr_の破棄の相対的な順序であり、これは観察できません。

6
ecatmur