いくつかのコードをリファクタリングしながら、std :: stringを返すいくつかのgetterメソッドに出会いました。たとえば、次のようなものです。
class foo
{
private:
std::string name_;
public:
std::string name()
{
return name_;
}
};
確かに、ゲッターはconst std::string&
?現在のメソッドは、効率的ではないコピーを返しています。代わりにconst参照を返すと問題が発生しますか?
これが問題を引き起こす唯一の方法は、呼び出し側が文字列をコピーするのではなく、参照を保存し、オブジェクトが破棄された後にそれを使用しようとする場合です。このような:
foo *pFoo = new foo;
const std::string &myName = pFoo->getName();
delete pFoo;
cout << myName; // error! dangling reference
ただし、既存の関数はコピーを返すため、既存のコードを壊すことはありません。
実際、別の問題具体的には文字列notを参照で返す場合、std::string
は、 c_str() メソッドを介した内部const char*
へのポインターを介したアクセスを提供します。これにより、長時間のデバッグで頭痛がしました。たとえば、fooから名前を取得し、JNIに渡し、jstringを構築してJava後で、そして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()メソッドに厄介な警告コメントヘッダーを付ける方がよい場合があります。以前のJavaコーダーは、このタイプのメソッドチェーンに対して特に脆弱である可能性があり、そうでなければ無害に見える可能性があるため、JNIについて言及します。
Const参照の戻りの1つの問題は、ユーザーが次のようなコードを記述した場合です。
const std::string & str = myObject.getSomeString() ;
とともに 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参照の戻りが優先されます(とにかく、コンパイラが余分な一時ファイルを最適化することを望んでいるよりも、参照を送信するほうが快適です)。私のオブジェクトの存在、彼らは私のオブジェクトの破壊の前にそれをコピーします」
Std :: stringの一部の実装は、コピーオンライトセマンティクスとメモリを共有するため、値による戻りは、参照による戻りとほぼ同じくらい効率的ですand心配する必要はありませんライフタイムの問題(ランタイムがそれを行います)。
パフォーマンスが心配な場合は、ベンチマーク(<=十分にストレスをかけることはできません)!!!両方のアプローチを試して、ゲイン(またはその不足)を測定します。どちらかが優れていて、本当に気にするなら、それを使用してください。そうでない場合は、他の人が言及した生涯の問題に対して、価値のある保護を優先します。
あなたは彼らが仮定を立てることについて何を言っているか知っています...
さて、コピーを返すことと参照を返すことの間のdifferencesは次のとおりです。
パフォーマンス:参照を返すのが速くなる場合と、そうでない場合があります。 (他の人が指摘したように)_std::string
_がコンパイラ実装によってどのように実装されるかに依存します。ただし、参照を返した場合でも、関数呼び出し後の割り当てには、通常std::string name = obj.name();
のようにコピーが含まれます。
安全性:参照を返すと、問題が発生する場合と発生しない場合があります(参照がぶら下がる)。関数のユーザーが何をしているのかわからない場合は、参照として参照を保存し、提供オブジェクトがスコープ外になった後にそれを使用すると、問題が発生します。
必要に応じて高速かつ安全を使用してくださいboost :: shared_ptr。オブジェクトは、文字列を_shared_ptr
_として内部的に保存し、_shared_ptr
_を返すことができます。そうすれば、オブジェクトのコピーは行われず、常に安全です(ユーザーがget()
で生のポインタを引き出し、オブジェクトがスコープから外れた後にそれを処理しない限り)。
Const std :: string&を返すように変更します。呼び出し元のコードをすべて変更しなければ、呼び出し元はおそらく結果のコピーを作成しますが、問題は発生しません。
Name()を呼び出す複数のスレッドがある場合、1つの潜在的なしわが発生します。参照を返した後、基になる値を後で変更すると、呼び出し元の値が変更されます。しかし、既存のコードはとにかくスレッドセーフに見えません。
関連する可能性は低いが可能性の低い問題に対するDimaの回答をご覧ください。
呼び出し元が本当にコピーを望んでいる場合、元のファイルを変更しようとしていて、そのコピーを保存したかったので、何かを壊すことができると考えられます。ただし、実際には、const参照を返すだけである可能性がはるかに高くなります。
最も簡単なことは、それを試してから、テストして実行できるかどうかを確認することです。そうでない場合は、リファクタリングを続行する前に、最初にテストを作成することに集中します。
それは重要ですか?最新の最適化コンパイラを使用するとすぐに、値を返す関数は、セマンティックに必要な場合を除き、コピーを必要としません。
これについては C++ lite FAQ をご覧ください。
Const参照に変更しても、その関数の一般的な使用が中断しないというオッズはかなり良いです。
その関数を呼び出すすべてのコードが管理下にある場合は、変更を加えて、コンパイラーが文句を言うかどうかを確認してください。
メンバーへの参照を返すと、クラスの実装が公開されます。それはクラスを変更することを防ぐことができます。最適化が必要な場合、プライベートまたは保護されたメソッドに役立つことがあります。 C++ゲッターは何を返すべきか
何をする必要があるかによります。クラスを変更せずにすべての呼び出し元に戻り値を変更したい場合があります。飛行しないconst参照を返す場合。
もちろん、次の引数は、呼び出し側が独自のコピーを作成できるということです。しかし、関数がどのように使用されるかを知っていて、それがとにかく起こることを知っているなら、多分これをすることはコードのあとのステップを節約するかもしれません。
できない場合を除き、通常const&を返します。 QBziZは、これが当てはまる場合の例を示します。もちろん、QBziZはstd :: stringがcopy-on-writeセマンティクスを持っていると主張していますが、これはCOWがマルチスレッド環境で多くのオーバーヘッドを伴うため、今日ではほとんど当てはまりません。 const&を返すことにより、呼び出し側に責任を負わせ、文字列の最後で正しいことを行います。ただし、すでに使用されているコードを扱っているため、この文字列のコピーがパフォーマンスの大きな問題を引き起こしていることがプロファイリングによって示されない限り、おそらく変更しないでください。それを変更することにした場合は、何も壊していないことを確認するために十分にテストする必要があります。うまくいけば、一緒に仕事をする他の開発者は、Dimaの答えのような大ざっぱなことをしないでください。