116

を操作する必要がある関数がある場合、その関数へshared_ptrの参照を渡す方が効率的ではないでしょうか (shared_ptrオブジェクトのコピーを避けるため)。考えられる悪い副作用は何ですか? 次の 2 つのケースが考えられます。

1) 関数内で、次のように引数のコピーが作成されます。

ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)  
{  
     ...  
     m_sp_member=sp; //This will copy the object, incrementing refcount  
     ...  
}  

2) 関数内では、次のように引数のみが使用されます。

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}  

どちらの場合も、参照ではboost::shared_ptr<foo>なく値で渡す正当な理由がわかりません。値渡しは、コピーのために参照カウントを「一時的に」インクリメントするだけで、関数スコープを終了するときにデクリメントします。私は何かを見落としていますか?

明確にするために、いくつかの回答を読んだ後:私は時期尚早の最適化の懸念に完全に同意し、常に最初にプロファイルしてからホットスポットで作業しようとします. 私の質問は、純粋に技術的なコードの観点からのものでした。

4

17 に答える 17

117

投票数の多い回答に反対していることに気づいたので、専門家の意見を探しに行きました。http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anythingから

ハーブサッター:「shared_ptrsを渡すと、コピーは高価になります」

Scott Meyers:「valuesで渡すか、参照で渡すかに関しては、shared_ptrに特別なことは何もありません。他のユーザー定義タイプで使用するのとまったく同じ分析を使用してください。人々はshared_ptrが何らかの形で解決するというこの認識を持っているようです。すべての管理上の問題、そしてそれは小さいので、価値で渡すのは必然的に安価です。それはコピーする必要があり、それに関連するコストがあります...価値で渡すのは高価なので、私が逃げることができれば私のプログラムでは適切なセマンティクスを使用しているので、代わりにconstまたはreferenceを参照して渡します。」

ハーブサッター:「常にconstを参照して渡します。非常にまれに、呼び出したものが参照を取得したものを変更する可能性があることを知っているため、値を渡す可能性があります...パラメーターとしてコピーすると、ああとにかく生きているので、その参照カウントを上げる必要はほとんどありません。参照によって渡す必要があるので、そうしてください。」

更新:ハーブはここでこれを拡張しました:http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/、話の教訓はあなたが通過するべきではないということですがshared_ptrsは、「所有権の共有や譲渡など、スマートポインター自体を使用または操作する場合を除きます。」

于 2012-01-13T01:48:22.337 に答える
114

個別のインスタンスのポイントは、参照カウントが少なくとも 1 であるため、shared_ptrこれがスコープ内にある限り、それが指すオブジェクトがまだ存在することを (可能な限り) 保証することです。shared_ptr

Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
    // sp points to an object that cannot be destroyed during this function
}

したがって、への参照を使用shared_ptrすることで、その保証を無効にします。したがって、2番目のケースでは:

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}

sp->do_something()ヌルポインターが原因で爆発しないことをどのように知っていますか?

それはすべて、コードの「...」セクションに何があるかによって異なります。最初の '...' で何かを呼び出すと、(コードの別の部分のどこかで)shared_ptr同じオブジェクトに対して a をクリアするという副作用がありますか? shared_ptrそして、それがたまたまそのオブジェクトとは別個に残っている唯一のものである場合はどうなるでしょうか? さようならオブジェクト、ちょうどあなたがそれを使おうとしているところで。

したがって、その質問に答えるには 2 つの方法があります。

  1. 関数本体でオブジェクトが死なないことを確認するまで、プログラム全体のソースを注意深く調べてください。

  2. パラメータを参照ではなく個別のオブジェクトに戻してください。

ここで適用される一般的なアドバイス: プロファイラーで現実的な状況で製品の時間を測定し、加えたい変更がパフォーマンスに大きな違い。

コメンターJQの更新

これは不自然な例です。意図的に単純化されているため、間違いは明らかです。実際の例では、間違いは実際の詳細の層に隠されているため、それほど明白ではありません。

メッセージをどこかに送信する関数があります。大きなメッセージになる可能性があるためstd::string、複数の場所に渡されるときにコピーされる可能性のある a を使用するのではshared_ptrなく、文字列に aを使用します。

void send_message(std::shared_ptr<std::string> msg)
{
    std::cout << (*msg.get()) << std::endl;
}

(この例では、コンソールに「送信」するだけです)。

次に、前のメッセージを記憶する機能を追加します。次の動作が必要です。変数は、最後に送信されたメッセージを含む必要がありますが、メッセージが現在送信されている間は、以前のメッセージがあってはなりません (送信前に変数をリセットする必要があります)。したがって、新しい変数を宣言します。

std::shared_ptr<std::string> previous_message;

次に、指定したルールに従って関数を修正します。

void send_message(std::shared_ptr<std::string> msg)
{
    previous_message = 0;
    std::cout << *msg << std::endl;
    previous_message = msg;
}

そのため、送信を開始する前に現在の前のメッセージを破棄し、送信が完了した後に新しい前のメッセージを保存できます。すべて良い。ここにいくつかのテストコードがあります:

send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);

Hi!予想どおり、これは2 回印刷されます。

次に、コードを見て考えたメンテナー氏がやって来send_messageますshared_ptr

void send_message(std::shared_ptr<std::string> msg)

明らかに、次のように変更できます。

void send_message(const std::shared_ptr<std::string> &msg)

これがもたらすパフォーマンスの向上を考えてみてください。(いくつかのチャネルを介して一般的に大きなメッセージを送信しようとしていることを気にしないでください。そのため、パフォーマンスの向上は非常に小さく、測定できません)。

しかし、実際の問題は、テスト コードが未定義の動作を示すことです (Visual C++ 2010 デバッグ ビルドでは、クラッシュします)。

メンテナ氏はこれに驚いていますがsend_message、問題の発生を阻止するために防御チェックを追加しています。

void send_message(const std::shared_ptr<std::string> &msg)
{
    if (msg == 0)
        return;

しかし、が呼び出されたmsgときに が null になることはないため、もちろん先に進んでクラッシュします。send_message

前述のように、すべてのコードが簡単な例のように密集しているため、間違いを見つけるのは簡単です。しかし、実際のプログラムでは、相互にポインターを保持する可変オブジェクト間のより複雑な関係があるため、間違いを犯しやすく間違いを検出するために必要なテスト ケースを作成するのは困難です。

関数が継続的に非 null であることに依存できるようにする簡単な解決策は、既存の への参照に依存するのではなくshared_ptr、関数が独自の true を割り当てることです。shared_ptrshared_ptr

欠点は、コピーされた ashared_ptrがフリーではないことです。「ロックフリー」の実装でさえ、スレッド化の保証を尊重するためにインターロック操作を使用する必要があります。shared_ptrそのため、を に変更することで、プログラムを大幅に高速化できる場合がありますshared_ptr &。しかし、これはすべてのプログラムに安全に適用できる変更ではありません。プログラムの論理的な意味を変更します。

の代わりに, および の代わりにstd::string全体を使用すると、同様のバグが発生することに注意してください。std::shared_ptr<std::string>

previous_message = 0;

メッセージをクリアするために、次のように言いました。

previous_message.clear();

その場合、症状は、未定義の動作ではなく、空のメッセージを誤って送信することになります。非常に大きな文字列の余分なコピーのコストは、 a をコピーするコストよりもはるかに重要になる可能性があるshared_ptrため、トレードオフは異なる場合があります。

于 2008-11-30T10:38:40.140 に答える
23

あなたとあなたが一緒に仕事をしている他のプログラマーが、あなたが何をしているのかを本当に、本当に知っているのでない限り、私はこのやり方に反対することをお勧めします。

まず、自分のクラスへのインターフェイスがどのように進化するかが分からず、他のプログラマーが悪いことをするのを防ぎたいと考えています。参照によって shared_ptr を渡すことは、慣用的ではないため、プログラマーが見るべきものではありません。防御的にプログラムする: インターフェイスを間違って使いにくくします。参照渡しは、後で問題を引き起こすだけです。

次に、この特定のクラスが問題になることがわかるまで最適化しないでください。最初にプロファイリングしてから、プログラムが参照渡しによるブーストを本当に必要としている場合は、そうするかもしれません。それ以外の場合は、小さなこと (つまり、値渡しに必要な追加の N 命令) に頭を悩ませるのではなく、設計、データ構造、アルゴリズム、および長期的な保守性について心配してください。

于 2008-11-29T16:32:32.390 に答える
18

はい、参照を取ることは問題ありません。メソッドに共有所有権を与えるつもりはありません。それはそれで働きたいだけです。とにかくコピーするので、最初のケースの参照も取得できます。しかし、最初のケースで、所有権が必要です。まだ一度だけコピーするこのトリックがあります:

void ClassA::take_copy_of_sp(boost::shared_ptr<foo> sp) {
    m_sp_member.swap(sp);
}

それを返すときもコピーする必要があります(つまり、参照を返さないでください)。あなたのクラスは、クライアントがそれを使って何をしているのかを知らないためです (それへのポインタを格納すると、ビッグバンが発生する可能性があります)。後でそれがボトルネックであることが判明した場合 (最初のプロファイル!)、参照を返すことができます。


編集:もちろん、他の人が指摘しているように、これはコードを知っていて、渡された共有ポインタを何らかの方法でリセットしないことがわかっている場合にのみ当てはまります。疑わしい場合は、値で渡します。

于 2008-11-29T14:34:29.993 に答える
11

shared_ptrsを通り過ぎるのが賢明const&です。問題が発生する可能性は低く ( shared_ptrEarwicker によって詳述されているように、関数呼び出し中に参照先が削除されるというまれなケースを除いて)、これらをたくさん渡すと高速になる可能性があります。覚えて; デフォルトboost::shared_ptrはスレッドセーフであるため、コピーするとスレッドセーフなインクリメントが含まれます。

一時オブジェクトは const 以外の参照では渡されない可能性があるためconst&、 だけではなく使用してみてください。&(MSVC の言語拡張により、とにかくそれを行うことができますが)

于 2008-11-30T14:57:43.750 に答える
10

2 番目のケースでは、これを行う方が簡単です。

Class::only_work_with_sp(foo &sp)
{    
    ...  
    sp.do_something();  
    ...  
}

次のように呼び出すことができます

only_work_with_sp(*sp);
于 2008-11-29T16:21:54.410 に答える
3

ここでのすべての長所と短所は、shared_ptr だけでなく、参照によって渡されるすべての型に実際に一般化できるようです。私の意見では、参照渡し、const 参照、および値渡しのセマンティクスを理解し、正しく使用する必要があります。しかし、すべての参照が悪いと思わない限り、参照によって shared_ptr を渡すことには本質的に何も問題はありません...

例に戻るには:

Class::only_work_with_sp( foo &sp ) //Again, no copy here  
{    
    ...  
    sp.do_something();  
    ...  
}

sp.do_something()ぶら下がっているポインターのために爆発しないことをどのように知っていますか?

sp真実は、shared_ptr であろうとなかろうと、const であろうとなかろうと、スレッド間で の所有権を直接的または間接的に共有する、 を実行するオブジェクトを誤って使用するdelete this、循環所有権またはその他の所有権エラーがあるなどの設計上の欠陥がある場合に、これが発生する可能性があります。

于 2010-11-29T00:02:27.557 に答える
3

const 参照によって共有ポインターを渡すことをお勧めします。これは、ポインターと共に渡される関数がポインターを所有していないというセマンティクスです。これは、開発者にとって明確なイディオムです。

唯一の落とし穴は、複数のスレッド プログラムで、共有ポインターが指すオブジェクトが別のスレッドで破棄されることです。したがって、共有ポインタの const 参照を使用することは、シングル スレッド プログラムでは安全であると言っても過言ではありません。

非 const 参照による共有ポインタの受け渡しは危険な場合があります。その理由は、関数が戻った後も有効と見なされるオブジェクトを破棄するために、関数が内部で呼び出す可能性のあるスワップ関数とリセット関数にあります。

時期尚早の最適化ではなく、何をしたいのかが明確であり、コーディングのイディオムが仲間の開発者によってしっかりと採用されている場合に、CPU サイクルの不必要な浪費を回避することです。

ちょうど私の2セント:-)

于 2010-03-28T19:37:28.183 に答える
3

関数が明示的にポインターを変更しない限り、「プレーンな」参照は避けます。

Aconst &は、小さな関数を呼び出す場合の賢明なマイクロ最適化である可能性があります。たとえば、いくつかの条件をインライン化するなど、さらなる最適化を有効にする場合などです。また、スレッドセーフであるため、インクリメント/デクリメントは同期ポイントです。ただし、これがほとんどのシナリオで大きな違いを生むとは思いません。

一般に、そうしない理由がない限り、より単純なスタイルを使用する必要があります。次に、 をconst &一貫して使用するか、少数の場所でのみ使用する場合はその理由についてコメントを追加します。

于 2008-11-29T19:12:27.780 に答える
1

Sandyは次のように書いています。「ここでのすべての長所と短所は、shared_ptrだけでなく、参照によって渡される任意のタイプに実際に一般化できるようです。」

ある程度は正しいですが、shared_ptrを使用するポイントは、オブジェクトの存続期間に関する懸念を排除し、コンパイラーにそれを処理させることです。参照によって共有ポインターを渡し、参照カウントされたオブジェクトのクライアントがオブジェクトデータを解放する可能性のあるnon-constメソッドを呼び出せるようにする場合、共有ポインターを使用してもほとんど意味がありません。

パフォーマンスが懸念される可能性があるため、前の文で「ほぼ」と書きました。まれに正当化される可能性がありますが、このシナリオを自分で回避し、真剣に検討するなど、他のすべての最適化ソリューションを自分で探します。別のレベルの間接参照、遅延評価などを追加する場合。

動作、特にオブジェクトの存続期間に関する動作についての暗黙の仮定を必要とし、明確で簡潔で読みやすいドキュメントを必要とする、または作成者の記憶を投稿するコードは、とにかくそれを読まないでしょう!シンプルさはほとんどの場合効率よりも優先され、効率を上げるには他の方法もほとんどあります。参照カウントオブジェクト(およびequals演算子)のコピーコンストラクターによるディープコピーを回避するために参照によって値を渡す必要がある場合は、ディープコピーされたデータを参照カウントポインターにする方法を検討する必要があります。すばやくコピーしました。(もちろん、それはあなたの状況に当てはまらないかもしれない1つの設計シナリオにすぎません)。

于 2011-09-02T19:46:53.760 に答える
1

const の正確性に関心がないと仮定すると (またはそれ以上、渡されるデータの所有権を関数が変更または共有できるようにすることを意味します)、boost::shared_ptr を値で渡すことは、次のように参照で渡すよりも安全です。元の boost::shared_ptr がそれ自身のライフタイムを制御できるようにします。次のコードの結果を検討してください...

void FooTakesReference( boost::shared_ptr< int > & ptr )
{
    ptr.reset(); // We reset, and so does sharedA, memory is deleted.
}

void FooTakesValue( boost::shared_ptr< int > ptr )
{
    ptr.reset(); // Our temporary is reset, however sharedB hasn't.
}

void main()
{
    boost::shared_ptr< int > sharedA( new int( 13 ) );
    boost::shared_ptr< int > sharedB( new int( 14 ) );

    FooTakesReference( sharedA );

    FooTakesValue( sharedB );
}

上記の例から、参照によってsharedAを渡すと、FooTakesReference が元のポインターをリセットできることがわかります。これにより、使用カウントが 0 に減り、データが破棄されます。ただし、FooTakesValueは元のポインターをリセットできないため、 sharedB のデータがまだ使用できることが保証されます。別の開発者が必然的にやって来て、 sharedA の脆弱な存在に便乗しようとすると、混乱が生じます。しかし、幸運なsharedB開発者は、彼の世界ではすべてが順調であるため、早く家に帰ります。

この場合、コードの安全性は、コピーによる速度の向上よりもはるかに重要です。同時に、boost::shared_ptr はコードの安全性を向上させることを目的としています。この種のニッチな最適化が必要な場合は、コピーから参照に移行する方がはるかに簡単です。

于 2010-03-03T14:24:51.610 に答える
1

時期尚早の最適化に精通していて、学術的な目的で、またはパフォーマンスが低下している既存のコードを分離したためにこれを求めていると仮定します。

参照渡しでもOK

const 参照による受け渡しの方が優れており、通常は使用できます。これは、指しているオブジェクトに const 性を強制しないためです。

参照を使用するためにポインターを失うリスクはありません。この参照は、スマート ポインターのコピーがスタックの前の方にあり、コール スタックを所有するスレッドは 1 つだけであるため、既存のコピーがなくなることはないという証拠です。

多くの場合、言及した理由により参照を使用する方が効率的ですが、保証されていません。オブジェクトの逆参照も手間がかかることに注意してください。参照を使用する理想的なシナリオは、コーディング スタイルに多くの小さな関数が含まれる場合で、ポインターが使用される前に関数から関数へと渡される場合です。

スマート ポインターを参照として保存することは常に避ける必要があります。あなたのClass::take_copy_of_sp(&sp)例は、その正しい使用法を示しています。

于 2008-11-30T16:00:36.107 に答える
0
struct A {
  shared_ptr<Message> msg;
  shared_ptr<Message> * ptr_msg;
}
  1. 値渡し:

    void set(shared_ptr<Message> msg) {
      this->msg = msg; /// create a new shared_ptr, reference count will be added;
    } /// out of method, new created shared_ptr will be deleted, of course, reference count also be reduced;
    
  2. 参照渡し:

    void set(shared_ptr<Message>& msg) {
     this->msg = msg; /// reference count will be added, because reference is just an alias.
     }
    
  3. ポインタ渡し:

    void set(shared_ptr<Message>* msg) {
      this->ptr_msg = msg; /// reference count will not be added;
    }
    
于 2013-07-23T07:23:47.450 に答える
0

litb が言ったことに加えて、2 番目の例ではおそらく const 参照によって渡されることを指摘したいと思います。そうすれば、誤って変更しないようにすることができます。

于 2008-11-29T17:36:21.153 に答える