参照または値によってスマートポインター(shared_ptr)を返す方法は?


94

を返すメソッドを持つクラスがあるとしますshared_ptr

参照または値によってそれを返すことの考えられる利点と欠点は何ですか?

2つの考えられる手がかり:

  • オブジェクトの早期破壊。shared_ptrby(const)参照を返す場合、参照カウンターは増分されないため、別のコンテキスト(たとえば、別のスレッド)でスコープから外れると、オブジェクトが削除されるリスクがあります。これは正しいです?環境がシングルスレッドの場合、この状況も発生する可能性がありますか?
  • 費用。値渡しは確かに無料ではありません。可能な限り回避する価値はありますか?

みんなありがとう。

回答:


114

スマートポインタを値で返します。

すでに述べたように、参照によってそれを返す場合、参照カウントを適切にインクリメントしないため、不適切なときに何かを削除するリスクが生じます。それだけで、参照によって戻らない十分な理由になるはずです。インターフェイスは堅牢でなければなりません。

戻り値の最適化(RVO)のおかげで、最近のコストの懸念は根本的ではないため、インクリメント、インクリメント、デクリメントのシーケンスなど、最新のコンパイラーでは発生しません。したがって、aを返す最善の方法shared_ptrは、単に値で返すことです。

shared_ptr<T> Foo()
{
    return shared_ptr<T>(/* acquire something */);
};

これは、最新のC ++コンパイラにとっては明らかに明白なRVOの機会です。すべての最適化がオフになっている場合でも、Visual C ++コンパイラーがRVOを実装していることは知っています。また、C ++ 11の移動セマンティクスでは、この懸念はさらに重要ではなくなります。(しかし、確実にする唯一の方法は、プロファイルを作成して実験することです。)

それでも確信が持てない場合は、Dave Abrahamsが値で返すことを主張する記事を公開してます。ここでスニペットを複製します。記事全体を読むことを強くお勧めします。

正直に言うと、次のコードはどのように感じますか?

std::vector<std::string> get_names();
...
std::vector<std::string> const names = get_names();

正直なところ、私はもっとよく知っているべきですが、それは私を緊張させます。原則的に、ときget_names() に戻り、我々はコピーする必要がありvectorstring秒。次に、を初期化するときに再度コピーする names必要があり、最初のコピーを破棄する必要があります。stringベクトルにNがある場合、文字列の内容がコピーされるため、コピーごとにN + 1ものメモリ割り当てと、キャッシュに適さない大量のデータアクセスが必要になる可能性があります。

私はそのような不安に立ち向かうのではなく、不要なコピーを避けるために、参照渡しに頼ることがよくあります。

get_names(std::vector<std::string>& out_param );
...
std::vector<std::string> names;
get_names( names );

残念ながら、このアプローチは理想からほど遠いです。

  • コードは150%増加しました
  • const名前を変更しているため、-ness を削除する必要がありました。
  • 関数型プログラマーが私たちに思い出させたいように、ミューテーションは、参照の透明性と方程式の推論を損なうことによって、コードを推論することをより複雑にします。
  • 名前の厳密な値のセマンティクスはなくなりました。

しかし、効率を上げるためにこの方法でコードをめちゃくちゃにすることが本当に必要なのでしょうか?幸い、答えはノー(特にC ++ 0xを使用している場合はノー)です。


参照で戻るとRVOが不可能になるので、RVOが質問の意味をなくしてしまうとは思いません。
エドワードストレンジ

@CrazyEddie:確かに、それが私がOPを値で返すことを推奨する理由の1つです。
インシリコ

標準で許可されているRVOルールは、標準で保証されている同期/前に発生した関係に関するルールよりも優先されますか?
edA-qa mort-ora-y 2012

1
@ edA-qa mort-ora-y:副作用がある場合でも、RVOは明示的に許可されます。たとえばcout << "Hello World!";、デフォルトのコピーコンストラクターにステートメントがある場合、Hello World!RVOが有効なときに2つのは表示されません。ただし、適切に設計されたスマートポインタの場合は、同期についても、これは問題になりません。
インシリコ

23

スマートポインター(shared_ptrだけでなく)について、参照を参照に返すことは受け入れられないと思います。参照または生のポインターでそれらを渡すことは非常にためらいます。どうして?後で参照を介してそれが浅いコピーされないことを確信できないからです。最初のポイントは、これが問題になる理由を定義しています。これは、シングルスレッド環境でも発生する可能性があります。プログラムに不正なコピーセマンティクスを設定するために、データに同時にアクセスする必要はありません。いったんポインターを渡した後は、ユーザーがポインターで何をするかを実際に制御することはないので、誤用によってAPIユーザーにぶら下がるのに十分なロープを与えないでください。

次に、可能であれば、スマートポインターの実装を確認します。建設と破壊はごくわずかに近いはずです。このオーバーヘッドが許容できない場合は、スマートポインターを使用しないでください。しかし、これを超えて、ポインタの使用を追跡するメカニズムへの相互に排他的なアクセスは、単にshared_ptrオブジェクトの構築よりも遅くなるため、取得した同時実行アーキテクチャも調べる必要があります。

編集、3年後:C ++のよりモダンな機能の出現により、呼び出し関数のスコープの外に存在することのないラムダを単に記述しただけでなく、別の場所にコピーしました。ここで、共有ポインタをコピーする際の最小限のオーバーヘッドを節約したい場合は、公平で安全です。どうして?参照が誤用されないことを保証できるからです。

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