なぜstd :: move an std :: shared_ptrなのでしょうか?


147

私はClangソースコードを調べていましたが、次のスニペットを見つけました。

void CompilerInstance::setInvocation(
    std::shared_ptr<CompilerInvocation> Value) {
  Invocation = std::move(Value);
}

なぜ私がしたいでしょうか?std::movestd::shared_ptr

共有リソースの所有権を譲渡するポイントはありますか?

なぜ私は代わりにこれをしないのですか?

void CompilerInstance::setInvocation(
    std::shared_ptr<CompilerInvocation> Value) {
  Invocation = Value;
}

回答:


136

他の答えが十分に強調しなかったのはスピードのポイントだと思います

std::shared_ptr参照カウントはアトミックです。参照カウントを増減するには、アトミックインクリメントまたはデクリメントが必要です。これは、非アトミックなインクリメント/デクリメントよりも100倍遅いことは言うまでもありません。同じカウンタをインクリメントおよびデクリメントすると、正確な数になり、プロセスに大量の時間とリソースが浪費されます。

shared_ptrをコピーする代わりに移動することにより、アトミック参照カウントを「スチール」し、もう一方を無効にしshared_ptrます。参照カウントを「盗む」ことはアトミックではなく、それをコピーするshared_ptr(そしてアトミックな参照の増分または減分を引き起こす)よりも100倍高速です。

この手法は純粋に最適化のために使用されることに注意してください。(あなたが提案したように)それをコピーすることは、機能的にも同様に素晴らしいです。


5
それは本当に倍速いですか?これのベンチマークはありますか?
xaviersjs 2019

1
@xaviersjs割り当てでは、Valueがスコープ外になったときに、アトミックインクリメントに続いてアトミックデクリメントが必要です。アトミック操作には数百のクロックサイクルが必要です。だからはい、それは本当にずっと遅いです。
Adisak

2
@Adisakは、フェッチおよび追加操作(en.wikipedia.org/wiki/Fetch-and-add)を聞いた最初の例であり、基本的な増分よりも数百サイクル以上かかる可能性があります。そのためのリファレンスはありますか?
xaviersjs

2
@xaviersjs:stackoverflow.com/a/16132551/4238087レジスタの操作が数サイクルであるため、アトミックの100サイクル(100〜300)で十分です。指標は2013年のものですが、マルチソケットNUMAシステムの場合は特にそうです。
russianfool

1
時々あなたはあなたのコードにスレッドがないと思うかもしれません...しかし、それからいくつかの気の利いたライブラリがやって来てあなたのためにそれを台無しにします。ポインタ参照カウントに依存するよりも、const参照とstd :: move ...が明確で、それが可能であることが明らかな場合...
Erik Aronesty

122

を使用moveすることで、シェア数の増加を避け、すぐに減少させることができます。これにより、使用回数に関する高価なアトミック操作を節約できます。


1
それは時期尚早の最適化ではありませんか?
YSC 2017年

11
@YSCは、そこに置いた人が実際にテストした場合はできません。
OrangeDog 2017年

19
@YSCは、コードの読み取りや保守を困難にする場合、時期尚早の最適化は悪です。これはどちらも、少なくともIMOは行いません。
AngewはSO

17
確かに。これは時期尚早の最適化ではありません。代わりに、この関数を書くための賢明な方法です。
オービットでの軽さのレース2017年

60

の移動操作(移動コンストラクターのような)std::shared_ptrは、基本的にソースから宛先へのポインタを盗む」ため、安価です。正確には、状態制御ブロック全体が、参照カウント情報を含め、ソースから宛先に「盗まれます」。 。

代わりに 、コピーの操作をstd::shared_ptr呼び出し、原子(すなわちだけでなく、参照カウントの増加++RefCount整数上のRefCountデータメンバが、例えば、呼び出し元InterlockedIncrementよりあるWindowsの場合)、高価ポインター/状態を単に盗むよりもます。

したがって、このケースの参照カウントダイナミクスを詳細に分析します。

// shared_ptr<CompilerInvocation> sp;
compilerInstance.setInvocation(sp);

sp値で渡し、メソッド内でコピーを取ると、次のようになりCompilerInstance::setInvocationます。

  1. メソッドに入るとき、shared_ptrパラメーターはコピー構築されます:ref count atomic increment
  2. メソッドの本体の中で、 shared_ptrパラメーターをデータメンバーにコピーします。ref count atomic increment
  3. メソッドを終了すると、 shared_ptrパラメーターは破棄されます:ref count atomic decrement

2つのアトミックインクリメントと1つのアトミックデクリメントがあり、合計3つの アトミック操作があります。

代わりに、 shared_ptrパラメーターを値でstd::moveメソッド内で(Clangのコードで正しく行われるように)と、次のようになります。

  1. メソッドを入力すると、 shared_ptrパラメーターはコピー構築されます:ref count atomic increment
  2. メソッドの本体の中で、 std::moveshared_ptrデータメンバへのパラメータは:refカウントがないではない変更します!ポインタ/状態を盗むだけです。高価なアトミック参照カウント操作は必要ありません。
  3. メソッドを終了すると、shared_ptrパラメーターは破棄されます。しかし、ステップ2で移動したので、shared_ptrパラメーターがもう何も指していないため、破壊するものはありません。この場合も、アトミックデクリメントは発生しません。

結論:この場合、参照カウントのアトミック増分は1つしか得られません。つまり、アトミック操作は1つだけです。
ご覧のように、これは、コピーの場合の2つのアトミックインクリメントと1つのアトミックデクリメント(合計3つのアトミック操作)よりもはるかに優れています。


1
また、注目に値します。なぜconst参照で渡して、std :: move全体を回避しないのですか?値渡しでは、生のポインターを直接渡すこともでき、作成されるshared_ptrは1つだけです。
ジョセフアイルランド

@JosephIreland const参照を移動できないため
Bruno Ferreira

2
@JosephIrelandは、このように呼び出すと増分compilerInstance.setInvocation(std::move(sp));がないためです。必要なオーバーロードを追加することで同じ動作を得ることができますが、必要がないときに複製する必要があります。shared_ptr<>&&
ラチェットフリーク

2
@BrunoFerreira私は自分の質問に答えていました。参照なので、移動する必要はありません。コピーするだけです。まだ2つではなく1つのコピーのみです。彼らがこれを行わない理由は、新しく作成されたshared_ptrsを、たとえばからsetInvocation(new CompilerInvocation)、またはラチェットが言及したように、不必要にコピーするためsetInvocation(std::move(sp))です。最初のコメントが不明確な場合は申し訳ありませんが、実際に書き込みを完了する前に誤って投稿しました。そのまま残すことにしました
Joseph Ireland

22

のコピーにshared_ptrは、内部状態オブジェクトポインタのコピーと参照カウントの変更が含まれます。これを移動するには、内部参照カウンターへのポインターと所有オブジェクトを交換するだけなので、より高速です。


16

この状況でstd :: moveを使用する理由は2つあります。ほとんどの応答は速度の問題に対処しましたが、コードの意図をより明確に示すという重要な問題を無視しました。

std :: shared_ptrの場合、std :: moveは明確に指示先の所有権の譲渡を示しますが、単純なコピー操作では追加の所有者が追加されます。もちろん、元の所有者が所有権を放棄した場合(たとえば、std :: shared_ptrを破棄できるようにすることで)、所有権の譲渡は完了です。

std :: moveを使用して所有権を譲渡する場合、何が起こっているかは明らかです。通常のコピーを使用する場合、元の所有者が所有権をすぐに放棄することを確認するまで、意図した操作が転送であることは明らかではありません。おまけとして、所有権のアトミックな移行により、所有者の数が1つ増加した(および参照カウントの付随する変更)一時的な状態を回避できるため、より効率的な実装が可能です。


まさに私が探しているもの。他の回答がこの重要な意味の違いをどのように無視しているかに驚いた。スマートポインターはすべて所有権に関するものです。
qweruiop

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.