質問

shared_ptrで動作する必要がある関数がある場合、(boost::shared_ptr<foo>オブジェクトのコピーを避けるために)それへの参照を渡す方が効率的ではないでしょうか? 考えられる悪い副作用は何ですか? 次の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();  
    ...  
}  

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

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

役に立ちましたか?

解決

別個のshared_ptrインスタンスのポイントは、このsp->do_something()がスコープ内にある限り、その参照カウントが少なくとも1。

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

std::stringへの参照を使用することにより、その保証を無効にします。 2番目の場合:

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

nullポインターが原因でHi!が爆発しないことをどのように確認しますか?

すべては、コードの「...」セクションの内容に依存します。同じオブジェクトへのsend_messageをクリアするという副作用(コードの別の部分)がある最初の '...'の間に何かを呼び出すとどうなりますか?そして、それがたまたまそのオブジェクトに残っている唯一のmsgである場合はどうでしょうか?さようならオブジェクト、ちょうどあなたがそれを試して使用しようとしている場所。

その質問に答えるには2つの方法があります:

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

  2. パラメータを変更して、参照ではなく個別のオブジェクトに戻します。

ここに当てはまる一般的なアドバイス:プロファイラーで現実的な状況で製品のタイミングを計り、最終的に行う変更を最終的に測定するまで、パフォーマンスのためにリスクのある変更をコードに加えないでください。パフォーマンスに大きな違いをもたらします。

コメンターJQの更新

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

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

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

(<!> quot; send <!> quot;この例ではコンソールに送信します)。

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

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);

そして予想どおり、これは<=>を2回出力します。

次は、メンテナー氏です。メンテナーはコードを見て考えます。ねえ、<=>へのパラメーターは<=>:

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

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

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

これによりもたらされるパフォーマンスの向上を考えてください! (通常、いくつかのチャネルを介して大きなメッセージを送信しようとしているので、パフォーマンスの向上は測定できないほど小さくなります。)

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

Mr Maintainerはこれに驚いていますが、問題の発生を止めるために、<=>に防御チェックを追加します。

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

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

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

簡単に解決できる方法は、関数が<=>に依存し続け、全体がnull以外であり続けるようにする場合、関数が既存の<=>。

マイナス面は、コピーされた<=>はフリーではないことです:<!> quot; lock-free <!> quot;実装では、スレッド化の保証を順守するためにインターロック操作を使用する必要があります。そのため、<=>を<=>に変更することでプログラムを大幅に高速化できる場合があります。しかし、これはすべてのプログラムに安全に行える変更ではありません。プログラムの論理的な意味を変更します。

<=>および次の代わりに<=>を使用すると、同様のバグが発生することに注意してください:

previous_message = 0;

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

previous_message.clear();

その場合、未定義の動作ではなく、誤って空のメッセージが送信されるという症状になります。非常に大きな文字列の余分なコピーのコストは、<=>のコピーのコストよりもはるかに大きいため、トレードオフが異なる場合があります。

他のヒント

最高得票の回答に同意しないことに気付いたので、専門家の意見を探しに行きました。 からhttp://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything

Herb Sutter:<!> quot; shared_ptrを渡すと、コピーは高価になります<!> quot;

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

Herb Sutter:<!> quot;それらは常にconstへの参照で渡されますが、非常にまれに、呼び出し元が参照を取得したものを変更する可能性があることを知っているため、値で渡す場合があります... ifそれらをパラメータとしてコピーします。参照カウントはとにかく保持されているため、参照カウントを上げる必要はほとんどありません。参照で渡す必要があるので、それを行ってください<!> quot;

更新:ここでハーブが拡張されました: http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ 。ただし、物語の教訓は、shared_ptrをまったく渡さないことです<! > quot;所有権を共有または譲渡するなど、スマートポインター自体を使用または操作する場合を除きます。<!> quot;

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

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

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

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

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

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


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

shared_ptrconst& sを渡すのが賢明です。おそらく問題を引き起こすことはありません(参照されたboost::shared_ptrが関数呼び出し中に削除されるというまれなケースを除き、Earwickerによって詳細が説明されています)。覚えておいてください。デフォルトの&はスレッドセーフなので、コピーするとスレッドセーフの増分が含まれます。

一時的なオブジェクトは非const参照によって渡されない可能性があるため、単なる<=>ではなく<=>を使用してください。 (MSVCの言語拡張機能を使用すると、とにかくそれを行うことができます)

2番目の場合、これを行う方が簡単です:

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

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

only_work_with_sp(*sp);

<!> quot; plain <!> quot;は避けたい関数が明示的にポインターを変更しない限り、参照します。

A const &は、小さな関数を呼び出すときの賢明なマイクロ最適化です。いくつかの条件をインライン化するなど、さらなる最適化を可能にします。また、増分/減分は(スレッドセーフなので)同期ポイントです。しかし、これがほとんどのシナリオで大きな違いをもたらすとは思わないでしょう。

通常、そうする理由がない限り、よりシンプルなスタイルを使用する必要があります。次に、一貫して<=>を使用するか、いくつかの場所で使用する理由についてコメントを追加します。

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

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

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

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

ちょうど2セント:-)

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

例に戻るには:

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

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

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

まだ言及されていないことの1つは、共有ポインターを参照で渡すと、派生クラスの共有ポインターを基本クラスの共有ポインターへの参照を介して渡す場合に得られる暗黙の変換が失われることです。 。

たとえば、このコードはエラーを生成しますが、共有ポインタが参照によって渡されないようにtest()を変更しても機能します。

#include <boost/shared_ptr.hpp>

class Base { };
class Derived: public Base { };

// ONLY instances of Base can be passed by reference.  If you have a shared_ptr
// to a derived type, you have to cast it manually.  If you remove the reference
// and pass the shared_ptr by value, then the cast is implicit so you don't have
// to worry about it.
void test(boost::shared_ptr<Base>& b)
{
    return;
}

int main(void)
{
    boost::shared_ptr<Derived> d(new Derived);
    test(d);

    // If you want the above call to work with references, you will have to manually cast
    // pointers like this, EVERY time you call the function.  Since you are creating a new
    // shared pointer, you lose the benefit of passing by reference.
    boost::shared_ptr<Base> b = boost::dynamic_pointer_cast<Base>(d);
    test(b);

    return 0;
}

早期最適化に精通していると仮定しますアカデミック目的で、またはパフォーマンスの低い既存のコードを分離したためにこれを求めています。

参照渡しは問題ありません

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

参照を使用しているためにポインタを失うリスクはありません。その参照は、スタックの初期にスマートポインターのコピーがあり、1つのスレッドだけが呼び出しスタックを所有しているため、既存のコピーが消えないという証拠です。

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

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

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はコードの安全性を向上させることを目的としています。この種のニッチな最適化が必要な場合は、コピーから参照に移動する方がはるかに簡単です。

Sandyは次のように書いた:<!> quot;ここのすべての長所と短所は、shared_ptrだけでなく、参照によって渡される任意の型に実際に一般化できるようです。<!> quot;

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

<!> quot; almost <!> quot;と書きました。その理由は、パフォーマンスが懸念される可能性があり、まれに正当化される可能性があるためですが、私はこのシナリオを自分で回避し、他のレベルの追加を真剣に検討するなど、他のすべての最適化ソリューションを自分で探しますインダイレクション、遅延評価など。

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

私は、スマートポインタを値で渡すという原則が非常に強いというプロジェクトで働いていました。パフォーマンス分析を行うように頼まれたとき-スマートポインターの参照カウンターのインクリメントとデクリメントのために、アプリケーションが使用されたプロセッサ時間の4〜6%を費やすことがわかりました。

Daniel Earwickerに記載されているように、奇妙なケースで問題が発生するのを避けるために、値でスマートポインターを渡したい場合は、支払う価格を必ず確認してください。

参照を使用することにした場合、const参照を使用する主な理由は、インターフェイスで使用するクラスを継承するクラスからオブジェクトに共有ポインターを渡す必要があるときに、暗黙のアップキャストを可能にするためです。

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

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;
    }
    

すべてのコードは意味を持たなければなりません。アプリケーションの値 everywhere で共有ポインタを渡す場合、これは <!> quot;を意味します。他の場所で何が起こっているのかわからないので、生の安全性を優先します <! > quot;。これは、コードを参照できる他のプログラマーにとって、私が自信を持ってサインするものではありません。

とにかく、関数がconst参照を取得し、あなたが<!> quot; unsure <!> quot;であっても、関数の先頭に共有ポインターのコピーを作成して、強い参照を追加できますポインターへ。これは、デザインに関するヒントとして見ることもできます(<!> quot;ポインターは他の場所で変更できます<!> quot;)。

そうです、IMO、デフォルトは<!> quot; const参照で渡す <!> quot;である必要があります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top