違いは、std::make_shared
1つのヒープ割り当てを実行するのに対して、std::shared_ptr
コンストラクターを呼び出すと2つ実行されることです。
ヒープ割り当てはどこで発生しますか?
std::shared_ptr
2つのエンティティを管理します。
- 制御ブロック(参照カウント、タイプ消去された削除などのメタデータを格納します)
- 管理されるオブジェクト
std::make_shared
制御ブロックとデータの両方に必要なスペースを考慮して、単一のヒープ割り当てを実行します。もう1つのケースでnew Obj("foo")
は、管理対象データのヒープ割り当てを呼び出し、std::shared_ptr
コンストラクターが制御ブロックに対して別の割り当てを実行します。
詳細については、cppreferenceの実装に関する注意事項を確認してください。
アップデートI:例外安全
注(2019/08/30):関数の引数の評価順序が変更されたため、これはC ++ 17以降の問題ではありません。特に、関数の各引数は、他の引数を評価する前に完全に実行する必要があります。
OPは例外の安全性について疑問を持っているようなので、答えを更新しました。
この例を考えてみましょう。
void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));
C ++では部分式の評価に任意の順序を使用できるため、可能な順序の1つは次のとおりです。
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
ここで、ステップ2で例外がスローされたとします(たとえば、メモリ不足例外、Rhs
コンストラクターがいくつかの例外をスローしました)。次に、クリーンアップする機会がなかったため、手順1で割り当てたメモリを失います。ここでの問題の核心は、生のポインタがstd::shared_ptr
コンストラクタにすぐに渡されなかったことです。
これを修正する1つの方法は、別の行でそれらを実行して、この任意の順序付けが発生しないようにすることです。
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
もちろん、これを解決するための好ましい方法は、std::make_shared
代わりにを使用することです。
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
アップデートII:の欠点 std::make_shared
Caseyのコメントを引用:
割り当ては1つしかないため、制御ブロックが使用されなくなるまで、指示先のメモリの割り当てを解除できません。A weak_ptr
は、制御ブロックを無期限に存続させることができます。
weak_ptr
sのインスタンスが制御ブロックを存続させるのはなぜですか?
がweak_ptr
管理対象オブジェクトがまだ有効かどうかを判断する方法が必要です(例:for lock
)。shared_ptr
これは、制御ブロックに格納されている管理対象オブジェクトを所有するの数をチェックすることによって行われます。その結果、制御ブロックは、shared_ptr
カウントとweak_ptr
カウントの両方が0になるまで有効です。
戻る std::make_shared
以来、std::make_shared
制御ブロックと、管理対象オブジェクトの両方のための単一のヒープ割り当てを行う、独立して制御ブロックと管理オブジェクトのメモリを解放する方法はありません。制御ブロックと管理対象オブジェクトの両方を解放できるようになるまで待つ必要があります。これは、shared_ptr
sまたはweak_ptr
s が存在しなくなるまで起こります。
代わりにnew
、shared_ptr
コンストラクターを介して制御ブロックと管理対象オブジェクトに2つのヒープ割り当てを実行したとします。次に、shared_ptr
s が存在しない場合は管理対象オブジェクトのメモリを解放し(多分早い)、s が存在しない場合は制御ブロックのメモリを解放します(多分後で)weak_ptr
。