Clangソースコード を見てきましたが、このスニペットが見つかりました:
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = std::move(Value);
}
std::move
をstd::shared_ptr
にしたいのはなぜですか?
共有リソースの所有権を譲渡するポイントはありますか?
代わりにこれをやらないのはなぜですか?
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = Value;
}
私は他の答えが十分に強調しなかった一つのことは、speedのポイントだと思います。
std::shared_ptr
の参照カウントはatomicです。参照カウントの増減requiresatomicincrementまたはdecrement。これは100倍slowよりもnon-atomicインクリメント/デクリメントではなく、同じカウンタをインクリメントまたはデクリメントすると、正確な数になり、プロセスで膨大な時間とリソースを浪費することになります。
shared_ptr
をコピーする代わりに移動することにより、atomic参照カウントを「盗み」、他のshared_ptr
を無効にします。参照カウントを「盗む」ことはatomicではなく、shared_ptr
をコピーする(そしてを引き起こすよりも100倍高速です) atomic参照の増分または減分)。
この手法は、最適化のためにのみ使用されることに注意してください。 (あなたが提案したように)それをコピーすることは、機能的にも同じように素晴らしいです。
move
を使用することで、共有の数を増やしてからすぐに減らすことを避けます。これにより、使用回数に関する高価なアトミック操作を節約できます。
std::shared_ptr
のMove操作(ムーブコンストラクターなど)はcheapです。これは基本的に"stealing pointers"(ソースから)宛先へ;より正確には、状態制御ブロック全体が、参照カウント情報を含め、ソースから宛先へ「盗まれ」ます。
代わりにcopyのstd::shared_ptr
の操作はatomic参照カウントの増加を呼び出します(つまり、整数のRefCount
データメンバーの++RefCount
だけでなく、WindowsのInterlockedIncrement
の呼び出しなど)単なるポインタ/状態を盗むよりもexpensiveより多く。
したがって、このケースの参照カウントダイナミクスを詳細に分析します。
// shared_ptr<CompilerInvocation> sp;
compilerInstance.setInvocation(sp);
sp
を値で渡し、CompilerInstance::setInvocation
メソッド内でcopyを使用すると、次のようになります。
shared_ptr
パラメーターはコピー構成されます:ref countatomicincrement。shared_ptr
パラメーターをcopy入れます:ref countatomicincrement。shared_ptr
パラメーターが破棄されます:ref countatomicdecrement。合計でthreeatomic操作の2つのアトミック増分と1つのアトミック減分があります。
代わりに、値でshared_ptr
パラメーターを渡し、メソッド内でstd::move
を渡すと(Clangのコードで適切に行われます)、次のようになります。
shared_ptr
パラメーターはコピー構成されます:ref countatomicincrement。std::move
パラメーターをshared_ptr
入れます:ref count doesnotchange!ポインタ/状態を盗むだけです。高価なアトミックrefカウント操作は必要ありません。shared_ptr
パラメーターは破棄されます。ただし、ステップ2で移動したため、shared_ptr
パラメーターが何も指し示していないため、破棄するものはありません。この場合も、アトミックな減少は発生しません。結論:この場合、ちょうどoneref count atomic increment、つまりone atomic操作のみを取得します。
ご覧のとおり、これはtwoアトミック増分プラスoneアトミックデクリメントよりもはるかにbetterですコピーケースの場合、合計threeアトミック操作)。
shared_ptr
のコピーには、その内部状態オブジェクトポインターのコピーと参照カウントの変更が含まれます。移動には、内部参照カウンターと所有オブジェクトへのポインターの交換のみが含まれるため、高速です。
この状況でstd :: moveを使用する理由は2つあります。ほとんどの応答は速度の問題に対処しましたが、コードの意図をより明確に示すという重要な問題を無視しました。
Std :: shared_ptrの場合、std :: moveはポインティの所有権の移転を明確に示しますが、単純なコピー操作は追加の所有者を追加します。もちろん、その後に元の所有者が所有権を放棄した場合(std :: shared_ptrの破棄を許可するなど)、所有権の譲渡は完了しています。
Std :: moveで所有権を譲渡すると、何が起きているかが明らかです。通常のコピーを使用する場合、元の所有者がすぐに所有権を放棄することを確認するまで、意図した操作が譲渡であることは明らかではありません。ボーナスとして、より効率的な実装が可能です。所有権のアトミック転送により、所有者の数が1増えた(および付随する参照カウントの変更)一時的な状態を回避できるためです。