73

いくつかのコードをリファクタリングしているときに、std::string を返す getter メソッドに出くわしました。たとえば、次のようなものです。

class foo
{
private:
    std::string name_;
public:
    std::string name()
    {
        return name_;
    }
};

const std::string&確かに、ゲッターは?を返す方が良いでしょう。現在のメソッドは効率的ではないコピーを返しています。代わりに const 参照を返すと問題が発生しますか?

4

12 に答える 12

60

これが問題を引き起こす可能性がある唯一の方法は、呼び出し元が文字列をコピーするのではなく参照を保存し、オブジェクトが破棄された後にそれを使用しようとする場合です。このような:

foo *pFoo = new foo;
const std::string &myName = pFoo->getName();
delete pFoo;
cout << myName;  // error! dangling reference

ただし、既存の関数はコピーを返すため、既存のコードを壊すことはありません。

編集:最新のC ++(つまり、C ++ 11以降)は戻り値の最適化をサポートしているため、値で物を返すことはもはや眉をひそめません。非常に大きなオブジェクトを値で返すことに注意する必要がありますが、ほとんどの場合は問題ありません。

于 2008-09-25T17:41:03.863 に答える
32

実際、参照によってではなく文字列を返すことに関するの問題は、 c_str()メソッドを介して内部へのポインターを介してアクセスを提供するという事実です。これにより、何時間もデバッグの頭痛の種になりました。たとえば、foo から名前を取得し、それを JNI に渡して、後で Java に渡す jstring の作成に使用するとします。これは参照ではなくコピーを返します。私は次のように書くかもしれません:std::stringconst char*name()

foo myFoo = getFoo(); // Get the foo from somewhere.
const char* fooCName = foo.name().c_str(); // Woops!  foo.name() creates a temporary that's destructed as soon as this line executes!
jniEnv->NewStringUTF(fooCName);  // No good, fooCName was released when the temporary was deleted.

呼び出し元がこの種のことを行う場合は、ある種のスマート ポインターまたは const 参照を使用するか、少なくとも foo.name() メソッドに厄介な警告コメント ヘッダーを設定することをお勧めします。私が JNI について言及したのは、以前の Java コーダーは、そうでなければ無害に見えるかもしれないこのタイプのメソッド連鎖に対して特に脆弱である可能性があるためです。

于 2011-02-23T18:19:54.780 に答える
18

const 参照戻りの問題の 1 つは、ユーザーが次のようなコードを作成した場合です。

const std::string & str = myObject.getSomeString() ;

return を使用するstd::stringと、str が範囲外になるまで、一時オブジェクトは存続し、str にアタッチされたままになります。

しかし、どうなりconst std::string &ますか?私の推測では、親オブジェクトが割り当てを解除すると死ぬ可能性のあるオブジェクトへの const 参照があると思います。

MyObject * myObject = new MyObject("My String") ;
const std::string & str = myObject->getSomeString() ;
delete myObject ;
// Use str... which references a destroyed object.

したがって、次の契約が尊重される限り、私の好みは const 参照リターンになります (とにかく、コンパイラが余分な一時を最適化することを期待するよりも、参照を送信する方が快適だからです)。私のオブジェクトの存在、彼らは私のオブジェクトの破壊の前にそれをコピーします。」

于 2008-09-25T20:58:25.090 に答える
10

std :: stringの一部の実装は、コピーオンライトセマンティクスを使用してメモリを共有するため、値による戻りは参照による戻りとほぼ同じくらい効率的であり、存続期間の問題について心配する必要はありません(ランタイムはあなたのために)。

パフォーマンスが心配な場合は、ベンチマークします(<=十分に強調することはできません)!!! 両方のアプローチを試して、ゲイン(またはその欠如)を測定します。1つが優れていて、本当に気にかけている場合は、それを使用してください。そうでない場合は、他の人が言及した生涯の問題に対して、それが提供する保護のために値によるものを好みます。

あなたは彼らが仮定をすることについて何を言っているか知っています...

于 2008-09-25T21:33:09.220 に答える
4

const std::string& を返すように変更します。すべての呼び出しコードを変更しなければ、呼び出し元はおそらく結果のコピーを作成しますが、問題は発生しません。

name() を呼び出す複数のスレッドがある場合、問題が発生する可能性があります。参照を返した後で基になる値を変更すると、呼び出し元の値が変更されます。しかし、とにかく既存のコードはスレッドセーフに見えません。

関連する可能性はあるがありそうもない問題については、Dima の回答をご覧ください。

于 2008-09-25T17:34:47.623 に答える
3

発信者がオリジナルを変更しようとしていて、そのコピーを保存したかったので、発信者が本当にコピーを望んでいた場合、何かを壊す可能性があると考えられます。ただし、実際には、const参照を返すだけである可能性がはるかに高くなります。

最も簡単な方法は、試してからテストして、実行できるテストがある場合に、それがまだ機能するかどうかを確認することです。そうでない場合は、リファクタリングを続行する前に、最初にテストを作成することに集中します。

于 2008-09-25T17:37:05.277 に答える
1

const参照に変更しても、その関数の一般的な使用法が壊れない可能性はかなり高いです。

その関数を呼び出すすべてのコードが制御下にある場合は、変更を加えて、コンパイラーが文句を言うかどうかを確認してください。

于 2008-09-25T17:43:13.343 に答える
1

それは問題ですか?最新の最適化コンパイラを使用するとすぐに、値によって返される関数は、意味的に必要でない限り、コピーを必要としません。

これについては、C++ lite FAQを参照してください。

于 2008-09-25T18:59:03.067 に答える
0

何をする必要があるかによって異なります。クラスを変更せずに、すべての呼び出し元に戻り値を変更させたい場合があります。飛ばない const 参照を返すと。

もちろん、次の議論は、呼び出し元が独自のコピーを作成できるということです。しかし、関数がどのように使用されるかを知っていて、とにかくそれが起こることがわかっている場合は、これを行うことでコードの後のステップを節約できます。

于 2008-09-25T17:35:41.600 に答える
0

できない場合を除き、通常は const& を返します。QBziZ は、これが当てはまる例を示しています。もちろん、QBziZ は std::string がコピー オン ライト セマンティクスを持っているとも主張していますが、COW はマルチスレッド環境で多くのオーバーヘッドを伴うため、今日ではめったに当てはまりません。const & を返すことで、呼び出し元に責任を負わせて、最後に文字列を使用して正しいことを行います。しかし、既に使用されているコードを扱っているので、この文字列のコピーが大きなパフォーマンス上の問題を引き起こしていることがプロファイリングによって示されない限り、おそらくコードを変更すべきではありません。次に、変更することにした場合は、何も壊していないことを確認するために徹底的にテストする必要があります. 一緒に仕事をしている他の開発者が、ディマの回答のような大ざっぱなことをしないことを願っています。

于 2008-09-25T18:33:16.843 に答える