C++ における make_shared と通常の shared_ptr の違い 質問する

C++ における make_shared と通常の shared_ptr の違い 質問する
std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

make_sharedを直接使用するよりも の方が効率的な理由がわかりませんshared_ptr

どのように効率的かを理解するために、作成されるオブジェクトのシーケンスと両方で実行される操作について段階的に説明する必要がありますmake_shared。参考までに、上に 1 つの例を示しました。

ベストアンサー1

違いは、 はstd::make_sharedヒープ割り当てを 1 回実行するのに対し、std::shared_ptrコンストラクターを呼び出すとヒープ割り当てが 2 回実行されることです。

ヒープ割り当てはどこで行われますか?

std::shared_ptr2 つのエンティティを管理します。

  • 制御ブロック(参照カウント、型消去された削除子などのメタデータを格納します)
  • 管理対象オブジェクト

std::make_shared制御ブロックとデータの両方に必要なスペースを考慮して、単一のヒープ割り当てを実行します。その他のケースでは、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 つは次のとおりです。

  1. new Lhs("foo"))
  2. new Rhs("bar"))
  3. std::shared_ptr<Lhs>
  4. 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

引用ケーシーのコメント:

割り当ては 1 つしかないため、制御ブロックが使用されなくなるまで、ポイント先のメモリの割り当てを解除することはできません。A はweak_ptr制御ブロックを無期限に維持できます。

weak_ptrs のインスタンスが制御ブロックを存続させるのはなぜですか?

weak_ptr管理対象オブジェクトがまだ有効かどうかを が判断する方法が必要です(例: の場合lock)。これは、管理対象オブジェクトを所有する の数 (制御ブロックに格納されている) をチェックすることによって行われますshared_ptr。その結果、制御ブロックは、カウントshared_ptrweak_ptrカウントの両方が 0 になるまで有効になります。

戻るstd::make_shared

std::make_sharedは制御ブロックと管理対象オブジェクトの両方に対して単一のヒープ割り当てを行うため、制御ブロックと管理対象オブジェクトのメモリを個別に解放する方法はありません。制御ブロックと管理対象オブジェクトの両方を解放できるようになるまで待つ必要がありますが、これはshared_ptrまたは がweak_ptrなくなるまで待つことになります。

new代わりに、コンストラクタを介して制御ブロックと管理対象オブジェクトに対して 2 つのヒープ割り当てを実行したとしますshared_ptr。その後、アクティブな がなくなったら管理対象オブジェクトのメモリを解放し (おそらくそれより早い段階で) shared_ptr、アクティブな がなくなったら制御ブロックのメモリを解放します (おそらくそれより遅い段階で) weak_ptr

おすすめ記事