質問
いくつかのコードをリファクタリングしているときに、std::string を返すゲッター メソッドをいくつか見つけました。たとえば次のようなものです。
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
ただし、既存の関数はコピーを返すため、既存のコードを壊すことはありません。
他のヒント
実は別の問題 具体的には 文字列を返す場合 ない 参考までに、事実は、 std::string
内部へのポインタを介したアクセスを提供します const char*
を介して c_str() 方法。このため、デバッグに何時間も悩まされることになりました。たとえば、foo から名前を取得し、それを JNI に渡し、後で Java に渡す jstring の構築に使用したいとします。 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 プログラマーが、一見無害に見えるこの種のメソッド チェーンに対して特に脆弱である可能性があるためです。
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 の一部の実装はコピーオンライト セマンティクスでメモリを共有するため、値による戻りは参照による戻りとほぼ同じ効率になります。 そして 存続期間の問題について心配する必要はありません (ランタイムが代わりにやってくれます)。
パフォーマンスが気になるなら、 それをベンチマークする (<= どれだけ強調しても足りません) !!!両方のアプローチを試して、ゲイン (またはゲインの不足) を測定します。どちらかが優れていて、本当に気になる場合は、それを使用してください。そうでない場合は、他の人が言及した生涯にわたる問題に対して提供される保護を価値として優先します。
仮定を立てることについて彼らが何と言っているかはご存知でしょう...
さて、それでは 違い コピーを返すことと参照を返すことの間には次のようなものがあります。
パフォーマンス:参照を返す方が速い場合もそうでない場合もあります。それはやり方次第です
std::string
(他の人が指摘したように) コンパイラ実装によって実装されます。ただし、参照を返したとしても、関数呼び出し後の代入には通常、次のようにコピーが含まれます。std::string name = obj.name();
安全性:参照を返すと、問題が発生する場合と発生しない場合があります (ダングリング参照)。関数のユーザーが、参照を参照として保存し、提供するオブジェクトがスコープ外になった後にそれを使用するということを行っていることを理解していない場合、問題が発生します。
もしそれを望むなら 速くて安全 使用 boost::shared_ptr. 。オブジェクトは内部的に文字列を次のように保存できます。 shared_ptr
そして、を返します shared_ptr
. 。そうすれば、オブジェクトのコピーは行われず、常に安全です (ユーザーが次のコマンドで生のポインタを取り出さない限り) get()
オブジェクトがスコープ外になった後にそれを使用して処理します)。
const std::string& を返すように変更します。呼び出し元のコードをすべて変更しない限り、呼び出し元はおそらく結果のコピーを作成しますが、問題は発生しません。
name() を呼び出すスレッドが複数ある場合、潜在的な問題が 1 つ発生します。参照を返した後、基になる値を変更すると、呼び出し元の値も変更されます。しかし、いずれにせよ、既存のコードはスレッドセーフではないようです。
関連する可能性はあるが可能性は低い問題に対する Dima の回答を見てください。
呼び出し元がオリジナルを変更しようとしており、そのコピーを保存したいため、本当にコピーが必要な場合は、何かを壊す可能性があると考えられます。ただし、実際には const 参照を返すだけである可能性がはるかに高くなります。
最も簡単な方法は、実行できる何らかのテストがある場合、それを試して、まだ機能するかどうかをテストすることです。そうでない場合は、リファクタリングを続ける前に、まずテストの作成に集中します。
関係ありますか?最新の最適化コンパイラを使用すると、意味的に必要でない限り、値によって返される関数にはコピーが含まれなくなります。
見る C++ ライトに関するよくある質問 これについて。
const 参照に変更しても、その関数の通常の使用法が壊れることはほとんどありません。
その関数を呼び出すすべてのコードが制御下にある場合は、変更を加えてコンパイラがエラーを起こすかどうかを確認してください。
何をする必要があるかによって異なります。おそらく、すべての呼び出し元でクラスを変更せずに戻り値を変更したいと思うかもしれません。const 参照を返すと飛ばされません。
もちろん、次の議論は、呼び出し側が独自のコピーを作成できるということです。ただし、関数がどのように使用されるかを知っていて、いずれにせよそれが起こることがわかっている場合は、これを行うことで、後のコードでの手順を節約できる可能性があります。
できない限り、通常は const& を返します。QBziZ は、これが当てはまる例を示しています。もちろん、QBziZ は、std::string にコピーオンライト セマンティクスがあるとも主張していますが、COW はマルチスレッド環境で多くのオーバーヘッドを伴うため、今日ではほとんど当てはまりません。const & を返すことにより、呼び出し側には文字列を正しく処理する責任が生じます。ただし、すでに使用されているコードを扱っているため、この文字列のコピーがパフォーマンスに重大な問題を引き起こしていることがプロファイリングで示されない限り、コードを変更すべきではありません。それを変更する場合は、徹底的にテストして、何も壊れていないことを確認する必要があります。あなたが一緒に働いている他の開発者が、Dimaの答えのような大ざっぱなことをしないことを願っています。
メンバーへの参照を返すと、クラスの実装が公開されます。そうなるとクラス変更ができなくなる可能性があります。最適化が必要な場合に、プライベート メソッドまたは保護されたメソッドで役立つ場合があります。C++ ゲッターは何を返すべきか