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_ptr
2 つのエンティティを管理します。
- 制御ブロック(参照カウント、型消去された削除子などのメタデータを格納します)
- 管理対象オブジェクト
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 つは次のとおりです。
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
引用ケーシーのコメント:
割り当ては 1 つしかないため、制御ブロックが使用されなくなるまで、ポイント先のメモリの割り当てを解除することはできません。A は
weak_ptr
制御ブロックを無期限に維持できます。
weak_ptr
s のインスタンスが制御ブロックを存続させるのはなぜですか?
weak_ptr
管理対象オブジェクトがまだ有効かどうかを が判断する方法が必要です(例: の場合lock
)。これは、管理対象オブジェクトを所有する の数 (制御ブロックに格納されている) をチェックすることによって行われますshared_ptr
。その結果、制御ブロックは、カウントshared_ptr
とweak_ptr
カウントの両方が 0 になるまで有効になります。
戻るstd::make_shared
std::make_shared
は制御ブロックと管理対象オブジェクトの両方に対して単一のヒープ割り当てを行うため、制御ブロックと管理対象オブジェクトのメモリを個別に解放する方法はありません。制御ブロックと管理対象オブジェクトの両方を解放できるようになるまで待つ必要がありますが、これはshared_ptr
または がweak_ptr
なくなるまで待つことになります。
new
代わりに、コンストラクタを介して制御ブロックと管理対象オブジェクトに対して 2 つのヒープ割り当てを実行したとしますshared_ptr
。その後、アクティブな がなくなったら管理対象オブジェクトのメモリを解放し (おそらくそれより早い段階で) shared_ptr
、アクティブな がなくなったら制御ブロックのメモリを解放します (おそらくそれより遅い段階で) weak_ptr
。