shared_ptrを参照または値のどちらで渡す必要がありますか?


270

関数がshared_ptr(boostまたはC ++ 11 STLから)取るとき、それを渡しますか?

  • const参照: void foo(const shared_ptr<T>& p)

  • または値によって: void foo(shared_ptr<T> p)

最初の方法の方が速いのではないかと思います。しかし、これは本当に価値があるのですか、それとも他に問題がありますか?

あなたが選択した理由を教えてください、または場合は、それが問題ではないと思う理由。


14
問題は、それらが同等ではないことです。参照バージョンは「エイリアスshared_ptrを設定します。必要に応じて変更できます。」と叫びますが、値バージョンは「コピーしますshared_ptrので、変更できる間はわかりません。 )const-referenceパラメーターが実際のソリューションであり、「someにエイリアスを設定しshared_ptr、変更しないことを約束します」(値によるセマンティクスと非常によく似ています!)
GManNickG

2
クラスのメンバーを返すことについての皆さんの意見に興味がありますshared_ptr。あなたはconst-refsでそれをしますか?
ヨハネスシャウブ-litb

3番目の可能性は、C ++ 0xでstd :: move()を使用することです。これにより、両方のshared_ptrがスワップされます
Tomaka17

@Johannes:コピー/参照カウントを避けるために、const-referenceでそれを返します。次に、プリミティブでない限り、すべてのメンバーをconst-referenceで返します。
GManNickG 2010

回答:


229

この質問は、C ++ and Beyond 2011のAsk Us AnythingセッションでScott、Andrei、Herbによって議論され、回答されています。パフォーマンスと正確さについては4:34からご覧ください。shared_ptr

簡単に言うと、目的がオブジェクトの所有権を共有することでない場合(たとえば、異なるデータ構造間、または異なるスレッド間)、値で渡す理由はありません

上記のリンクされたトークビデオでScott Meyersによって説明されているように移動最適化できなければ、それは使用できるC ++の実際のバージョンに関連しています。

GoingNative 2012カンファレンスのインタラクティブパネルで、このディスカッションが大幅に更新されましたこれは、特に22:50から見る価値があります。


5
しかし、ここに示されているように、値で渡す方が安上がりです。stackoverflow.com/ a / 12002668/128384も考慮に入れるべきではありません(少なくとも、shared_ptrがメンバーにされるコンストラクター引数などの場合)クラス)?
stijn

2
@stijnはい、いいえ。あなたが指摘するQ&Aは、それが参照するC ++標準のバージョンを明確にしない限り、不完全です。単に誤解を招く一般的な決して/常にルールを広めることは非常に簡単です。そうでない限り、読者はデビッドアブラハムの記事と参照に慣れるために時間をかけるか、投稿日と現在のC ++標準を考慮に入れてください。だから、私とあなたが指摘した答えはどちらも、投稿の時間を考えると正しいです。
mloskot

1
マルチスレッディングがない限り」いいえ、MTは決して特別なものではありません。
好奇心が強い人

3
私はパーティーにとても遅れていますが、shared_ptrを値渡ししたいのは、コードが短くてきれいなためです。真剣に。Value*短くて読みやすいですが、それは悪いので、私のコードはいっぱいにconst shared_ptr<Value>&なり、かなり読みにくくなり、ただ...整然としています。以前void Function(Value* v1, Value* v2, Value* v3)は何void Function(const shared_ptr<Value>& v1, const shared_ptr<Value>& v2, const shared_ptr<Value>& v3)でしたか、そして人々はこれで大丈夫ですか?
Alex

7
@Alex一般的な方法は、クラスの直後にエイリアス(typedef)を作成することです。あなたの例のために:class Value {...}; using ValuePtr = std::shared_ptr<Value>;そしてあなたの関数はより単純にvoid Function(const ValuePtr& v1, const ValuePtr& v2, const ValuePtr& v3)なります:そしてあなたは最大のパフォーマンスを得ます。それがC ++を使う理由ですね。:)
4LegsDrivenCat

92

ここだハーブサッターのテイク

ガイドライン:所有権の共有や譲渡など、スマートポインター自体を使用または操作する場合を除き、スマートポインターを関数パラメーターとして渡さないでください。

ガイドライン:関数が値によるshared_ptrパラメーターを使用してヒープオブジェクトの所有権を格納および共有することを示します。

ガイドライン:非const shared_ptr&パラメーターは、shared_ptrを変更する場合にのみ使用してください。コピーを取得して所有権を共有するかどうかわからない場合のみ、パラメーターとしてconst shared_ptr&を使用します。それ以外の場合は、代わりにwidget *(またはnull可能でない場合はwidget&)を使用します。


3
サッターへのリンクをありがとう。逸品です。C ++ 14が利用可能な場合は、オプションの<widget&>を使用することを好みますが、ウィジェット*については彼に同意しません。widget *は古いコードとあいまいすぎます。
2015年

3
可能性としてwidget *およびwidget&を含めるための+1。詳しく説明すると、widget *またはwidget&を渡すことは、関数がポインターオブジェクト自体を調べたり変更したりしていない場合、おそらく最良のオプションです。特定のポインタータイプを必要とせず、shared_ptr参照カウントのパフォーマンスの問題が回避されるため、インターフェイスはより一般的です。
tgnottingham 2015

4
2つ目のガイドラインがあるため、今日はこれが受け入れられる答えになると思います。それは明らかに現在受け入れられている答えを無効にします、それは言います:値で渡す理由はありません。
mbrt 2018

62

個人的にはconstリファレンスを使用します。関数呼び出しのために参照カウントを再びデクリメントするためだけに参照カウントをインクリメントする必要はありません。


1
私はあなたの回答に反対票を投じませんでしたが、これが好みの問題になる前に、考慮すべき2つの可能性のそれぞれに賛否両論があります。そして、これらの長所と短所を知り、議論することは良いことです。その後、誰もが自分で決定を下すことができます。
Danvil

@Danvil:動作を考慮して、shared_ptr参照渡しされないことの唯一の可能な欠点は、パフォーマンスのわずかな損失です。ここには2つの原因があります。a)ポインターエイリアシング機能とは、ポインター相当のデータとカウンター(弱い参照の場合は2)がコピーされることを意味するため、データラウンドをコピーする方が少しコストがかかります。b)アトミック参照カウントは、単純な古いインクリメント/デクリメントコードよりも少し遅いですが、スレッドセーフにするために必要です。それ以外は、2つの方法はほとんどの目的と目的で同じです。
エヴァンテラン2010

37

const参照渡し、より高速です。それを格納する必要がある場合は、たとえば、あるコンテナに入れてください。countは、コピー操作によって自動的にインクリメントされます。


4
それを支持する数字なしでその意見を否定投票する。
kwesolowski 2018

22

私は一度で、以下のコードを実行したfoo服用shared_ptrにより、const&そして再びでfoo取っshared_ptrた値で。

void foo(const std::shared_ptr<int>& p)
{
    static int x = 0;
    *p = ++x;
}

int main()
{
    auto p = std::make_shared<int>();
    auto start = clock();
    for (int i = 0; i < 10000000; ++i)
    {
        foo(p);
    }    
    std::cout << "Took " << clock() - start << " ms" << std::endl;
}

Intel Core 2 Quad(2.4GHz)プロセッサーでVS2015、x86リリースビルドを使用

const shared_ptr&     - 10ms  
shared_ptr            - 281ms 

値によるコピーのバージョンは、桁違いに遅いものでした。
現在のスレッドから同期的に関数を呼び出す場合は、const&バージョンを優先します。


1
使用したコンパイラ、プラットフォーム、最適化設定を教えてください。
Carlton

vs2015のデバッグビルドを使用し、今すぐリリースビルドを使用するように回答を更新しました。
tcb

1
最適化がオンになっているときに両方で同じ結果が得られるかどうか知りたい
Elliot Woods

2
最適化はあまり役に立ちません。問題は、コピーの参照カウントのロック競合です。
Alex

1
それはポイントではありません。このようなfoo()ことは、このオブジェクトを使用していないので、機能はしても最初の場所で共有ポインタを受け入れるべきではない:それは受け入れなければならないint&とやるp = ++x;呼び出し、foo(*p);からmain()。関数は、何かをする必要があるときにスマートポインターオブジェクトを受け入れます。ほとんどの場合、実行する必要があるのはstd::move()、それを別の場所に移動する()ため、値によるパラメーターにコストはかかりません。
eepp

15

C ++ 11以降では、あなたが考えているよりも頻繁にconst&よりも値でそれをとるべきです。

std :: shared_ptr(基礎となる型Tではなく)を使用している場合は、それを使用して何かを実行したいため、そうしています。

コピーしたい場合どこか、const&で取得して後でコピーするのではなく、コピーで取得してstd :: moveを内部で移動する方が理にかなっています。これは、関数を呼び出すときに呼び出し側にstd :: move shared_ptrを順に移動するオプションを許可し、インクリメントおよびデクリメント操作のセットを節約できるためです。か否か。つまり、関数の呼び出し元は、関数の呼び出し後にstd :: shared_ptrが必要かどうか、および移動するかどうかに応じて決定できます。これは、const&で渡す場合は達成できません。そのため、値で取得することをお勧めします。

もちろん、呼び出し元の両方が長い間shared_ptrを必要とし(したがって、std :: moveできない)、関数内にプレーンコピーを作成したくない場合(弱いポインターが必要な場合、または場合によっては必要な場合のみ)いくつかの条件に応じてそれをコピーするには)、const&の方が望ましい場合があります

たとえば、あなたはすべきです

void enqueue(std::shared<T> t) m_internal_queue.enqueue(std::move(t));

以上

void enqueue(std::shared<T> const& t) m_internal_queue.enqueue(t);

この場合、常に内部でコピーを作成するため


1

atomic_incrementとdecrementが存在するshared_copyコピー操作の時間コストがわからないため、CPU使用率の問題が非常に大きくなりました。アトミックなインクリメントとデクリメントにはそれほどのコストがかかるとは思いませんでした。

私のテスト結果によると、int32アトミックインクリメントおよびデクリメントは、非アトミックインクリメントおよびデクリメントよりも2または40倍かかります。私はWindows 8.1の3GHz Core i7でそれを手に入れました。前者は競合が発生しない場合、後者は競合の可能性が高い場合です。アトミック操作がハードウェアベースのロックであることに注意してください。ロックはロックです。競合が発生するとパフォーマンスが低下します。

これを経験して、私は常にbyval(shared_ptr)よりもbyref(const shared_ptr&)を使用しています。


1

最近のブログ投稿がありました:https : //medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce

したがって、これに対する答えは、(ほとんど)決して通り過ぎないことconst shared_ptr<T>&です。
代わりに、基本となるクラスを渡すだけです。

基本的に、妥当なパラメータタイプは次のとおりです。

  • shared_ptr<T> -変更して所有権を取得する
  • shared_ptr<const T> -変更せず、所有権を取得
  • T& -変更、所有権なし
  • const T& -変更しないでください、所有権はありません
  • T -変更しない、所有権なし、安価なコピー

@accelがhttps://stackoverflow.com/a/26197326/1930508で指摘したように、Herb Sutterからのアドバイスは次のとおりです。

コピーを取得して所有権を共有するかどうかわからない場合にのみ、パラメータとしてconst shared_ptr&を使用します

しかし、どれほどの場合、確信が持てませんか?これはまれな状況です


0

shared_ptrを値で渡すとコストがかかり、可能であれば回避する必要があることは既知の問題です。

shared_ptrを通過するコスト

ほとんどの場合、shared_ptrを参照渡し、さらにはconst参照で渡すと十分です。

cppコアガイドラインには、shared_ptrを渡すための特定のルールがあります。

R.34:関数がパーツ所有者であることを表現するために、shared_ptrパラメーターを受け取ります

void share(shared_ptr<widget>);            // share -- "will" retain refcount

shared_ptrを値で渡すことが本当に必要な場合の例は、呼び出し元が共有オブジェクトを非同期呼び出し先に渡す場合です。つまり、呼び出し先がジョブを完了する前に呼び出し元がスコープ外に出ます。呼び出し先は、値によってshare_ptrを取得することにより、共有オブジェクトの存続期間を「延長」する必要があります。この場合、shared_ptrへの参照を渡しても機能しません。

共有オブジェクトを作業スレッドに渡す場合も同様です。


-4

shared_ptrは十分な大きさではありません。また、参照による受け渡しとコピーによる受け渡しのパフォーマンスを考慮に入れるために、コピーから十分なオーバーヘッドを確保するために、そのコンストラクタ\デストラクタが十分な作業を行いません。


15
測定しましたか?
curiousguy '30 / 10/30

2
@stonemetal:新しいshared_ptrを作成する際のアトミック命令はどうですか?
Quarra 2014年

これは非PODタイプであるため、ほとんどのABIでは、「値渡し」でも、実際にはポインターを渡します。問題となるのは、バイトの実際のコピーではありません。asmの出力でわかるように、shared_ptr<int>by値を渡すと、100を超えるx86命令がかかります(lock参照カウントをアトミックにinc / decするための高価なed命令を含む)。定数refを渡すことは、ポインターを何かに渡すことと同じです(そして、この例ではGodboltコンパイラエクスプローラーで、末尾呼び出しの最適化により、呼び出しではなく単純なjmpに変換されます:godbolt.org/g/TazMBU)。
Peter Cordes

TL:DR:これはC ++であり、コピーコンストラクターはバイトをコピーするだけでなく、より多くの作業を実行できます。この答えは総ゴミです。
Peter Cordes

2
stackoverflow.com/questions/3628081/shared-ptr-horrible-speed例として、値で渡される共有ポインタと参照で渡される共有ポインタの実行時間の差は約33%であることがわかります。パフォーマンスが重要なコードに取り組んでいる場合、ネイキッドポインターを使用すると、パフォーマンスが大幅に向上します。したがって、覚えている場合はconst refを渡しますが、覚えていない場合はそれほど問題ではありません。必要ない場合は、shared_ptrを使用しないほうがはるかに重要です。
ストーンメタル2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.