1. ホーム
  2. c++

[解決済み] C++のmake_sharedと通常のshared_ptrの違いについて

2022-03-17 22:24:09

質問

std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

この件に関しては、googleやstackoverflowに多くの投稿がありますが、私はなぜ理解できないのでしょうか? make_shared を直接使用するよりも、より効率的です。 shared_ptr .

誰か、オブジェクトの作成と操作の順序をステップバイステップで説明してくれませんか? make_shared は効率的です。参考までに、上に1つの例を挙げました。

どのように解決するのですか?

その差は std::make_shared はヒープを 1 つ確保するのに対して std::shared_ptr コンストラクタは2回実行されます。

ヒープの確保はどこで行われるのですか?

std::shared_ptr は2つの実体を管理します。

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

std::make_shared は、制御ブロックとデータの両方に必要な領域を考慮した単一のヒープ割り当てを実行します。もう一方の場合は new Obj("foo") は管理されたデータに対してヒープ割り当てを行い std::shared_ptr コンストラクタは制御ブロックのために別のものを実行します。

さらに詳しい情報は 実装ノート 参照 .

アップデート 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 のコンストラクタですぐに実行します。

これを解決する一つの方法は、この恣意的な順序付けが起こらないように、それらを別々の行で行うことです。

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 はコントロールブロックを生かしますか?

の方法があるはずです。 weak_ptr は、管理対象オブジェクトがまだ有効であるかどうかを判断するために(例. lock ). これを行うには shared_ptr は、コントロールブロックに格納されている管理対象オブジェクトを所有する。その結果、コントロールブロックが生きている間は shared_ptr をカウントし weak_ptr のカウントがともに0になりました。

戻る std::make_shared

以降 std::make_shared はコントロールブロックとマネージドオブジェクトの両方に対して単一のヒープ割り当てを行うため、コントロールブロックとマネージドオブジェクトのメモリを別々に解放する方法がありません。コントロールブロックと管理されたオブジェクトの両方が解放されるまで待つ必要があります。 shared_ptr または weak_ptr が生きている。

その代わりに、制御ブロックと管理オブジェクトの2つのヒープ割り当てを、以下の方法で行ったとします。 newshared_ptr のコンストラクタを使用します。そして、管理対象オブジェクトのメモリを解放します(たぶんもっと早く)。 shared_ptr が存在しなくなったら制御ブロックのメモリを解放する(たぶん後)。 weak_ptr が生きている。