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
がどのように効率的であるかを理解できるようにするために、誰かが私に作成されたオブジェクトとその両方によって実行された操作のステップ順を追って説明することはできますか。私は参考のために上記の一例を挙げました。
違いは、std::make_shared
は1回のヒープ割り当てを実行するのに対し、std::shared_ptr
コンストラクタの呼び出しは2回実行することです。
std::shared_ptr
は2つのエンティティを管理します。
std::make_shared
は、制御ブロックとデータの両方に必要なスペースを考慮して、単一のヒープ割り当て会計を実行します。それ以外の場合、new Obj("foo")
は管理対象データ用のヒープ割り当てを呼び出し、std::shared_ptr
コンストラクターは制御ブロック用に別のコンストラクターを実行します。
詳細については、実装に関する注意事項を cppreference で確認してください。
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++では部分式の評価を任意の順序で行うことができるため、考えられる順序は次のとおりです。
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"));
std::make_shared
のデメリット引用 Casey さんのコメント:
割り当ては1つしかないため、制御ブロックが使用されなくなるまで、指示先のメモリの割り当てを解除することはできません。
weak_ptr
は、制御ブロックを無期限に存続させることができます。
weak_ptr
sのインスタンスが制御ブロックを生き続けているのはなぜですか?weak_ptr
sが管理対象オブジェクトがまだ有効かどうかを判断する方法がなければなりません(例えば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
が存在しない場合は管理オブジェクト用のメモリを解放し、生きているweak_ptr
が存在しない場合は制御ブロック用のメモリを解放します。
共有ポインタは、オブジェクト自体と、参照カウントとその他のハウスキーピングデータを含む小さなオブジェクトの両方を管理します。 make_shared
は、これら両方を保持するために単一のメモリブロックを割り当てることができます。ポインタからすでに割り当てられているオブジェクトへの共有ポインタを構築するには、参照カウントを格納するために2番目のブロックを割り当てる必要があります。
この効率性と同様に、make_shared
を使用することはnew
と生のポインタを扱う必要が全くないことを意味し、より良い例外安全性を与えます - オブジェクトを割り当てた後でスマートポインタに割り当てる前に例外を投げる可能性はありません。
非公開コンストラクタ(protectedまたはprivate)を呼び出す必要がある場合、make_sharedはそれにアクセスできない可能性がありますが、新しいバリアントではうまくいきますが、2つの可能性が異なることもあります。 。
class A
{
public:
A(): val(0){}
std::shared_ptr<A> createNext(){ return std::make_shared<A>(val+1); }
// Invalid because make_shared needs to call A(int) **internally**
std::shared_ptr<A> createNext(){ return std::shared_ptr<A>(new A(val+1)); }
// Works fine because A(int) is called explicitly
private:
int val;
A(int v): val(v){}
};
Shared_ptrによって制御されるオブジェクトに特別なメモリアライメントが必要な場合は、make_sharedに頼ることはできませんが、それを使用しないことの唯一の良い理由だと思います。
Shared_ptr
:2つのヒープ割り当てを行います
Make_shared
:ヒープ割り当てを1回だけ実行します
私はstd :: make_sharedに一つ問題があるのを見ます、それはプライベート/保護されたコンストラクタをサポートしません
効率性と割り当てに費やす時間について、以下のこの簡単なテストを行いました。これら2つの方法で(一度に1つずつ)多数のインスタンスを作成しました。
for (int k = 0 ; k < 30000000; ++k)
{
// took more time than using new
std::shared_ptr<int> foo = std::make_shared<int> (10);
// was faster than using make_shared
std::shared_ptr<int> foo2 = std::shared_ptr<int>(new int(10));
}
つまり、make_sharedを使用すると、newを使用するのに比べて2倍の時間がかかりました。したがって、newを使用すると、make_sharedを使用するのではなく、2つのヒープ割り当てがあります。たぶんこれは愚かなテストですが、make_sharedを使うのはnewを使うよりも時間がかかることを示していませんか?もちろん、私は時間だけについて話しています。