C ++ 11の値とconst変数による戻り

多くのプログラミング言語では、オブジェクトと変数を定数として宣言できます。 また、それに応じて、値を変更しない場合はそうすることをお勧めします。 新しい標準の出現により、C ++には値によって関数からオブジェクトを返すという推奨事項が登場しました。RVOがなくても、動作のセマンティクスを使用してプログラムのパフォーマンスを向上できるからです。 これらの2つの推奨事項を一緒に使用するとどうなりますか:値で定数オブジェクトを返しますか? さらに理解してみましょう。



最近のC ++ Nowカンファレンスのビデオを見て、ある興味深いポイント(落とし穴)に出会いました。 スピーチのいずれかの終わりに、スピーカーは次のコードを提供します。

struct s { ... }; sf() { const sr; ...; return r; }
      
      





そして、オブジェクトrが関数から戻ったときに何が起こるかを尋ねますか?



構造体sにコピーコンストラクターと移動コンストラクターの両方があり、関数fにRVOの使用を妨げるコードが含まれているとします。 関数から返されたオブジェクトrはどうなりますか? オブジェクトがconst指定子なしで作成された場合、関数から戻ったときに移動され、 constで作成された場合はコピーされます。 GCCとClang(Visual Studioではチェックしませんでした)でも同様の動作が見られました。 バグまたは予期される動作とは何ですか? オブジェクトがすぐに破壊された場合、動かさないのはなぜですか?



C ++ 03以降、私たちの多くは、一時オブジェクトと定数がほとんど同じものであるという事実に慣れています。 両方の動作が似ているため、これは論理的です:値または定数参照によって引数を取る関数のみを呼び出すことができます(または単にこれらの型に値を割り当てます):

 void f1(int a) { } void f2(int& a) { } void f3(const int& a) { } int g() { return 0; } int main() { f1(g()); // OK f2(g()); // compile error f3(g()); // OK const int a = 0; f1(a); // OK f2(a); // compile error f3(a); // OK }
      
      





C ++ 11では、状況が変わりました。右辺値参照を使用して一時オブジェクトを変更できるようになりました。 しかし、定数ではありません。 標準によれば、 const指定子で宣言されたオブジェクトへの変更(const_castを使用するか、ポインターを使用するperversionsを使用するかに関係なく)は、未定義の動作につながります。 コンパイラーがmoveコンストラクターを使用してコードを生成すると、プログラムは未定義の動作状態になり、これは受け入れられないことがわかります。 言い換えると、const-correctnessは、同様の最適化よりもコンパイラにとって優先度が高くなります。 それでも、C ++は厳密に型指定された言語であり、 constからnormalへの参照およびポインターの暗黙的なキャストは禁止されています。たとえば、 const A a-> A &&



次のコードを検討してください。

 void f1(int& a) { } void f2(const int& a) { } int main() { int a1 = 0; f1(a1); // OK int a2 = 0; f2(a2); // OK const int ca1 = 0; f1(ca1); // compile error const int ca2 = 0; f2(ca2); // OK }
      
      





C ++標準では、オブジェクトから修飾子を暗黙的に削除することは許可されていません。 特定のタイプのAA&const A&の両方に縮小できる場合、 const Aconst A&にのみ縮小できます。 次に、関数にパラメーターを渡すときに呼び出しを移動するstd :: move呼び出しを追加して、リンクの左辺値をリンクの右辺値に置き換えます。 const A aA &&にキャストすることはできませんが、 const A &&をキャストすることはできます。 このようなコンストラクターを記述する場合、コンパイラーはそれを使用して関数から定数変数を返しますが、これは通常のコピーコンストラクターからよりも意味がありません。 したがって、このタイプのリンクは通常使用されません。



おわりに


結局のところ、いくつかの推奨プラクティスは組み合わされていません。 C ++では、別の落とし穴につまずかないように、常にあちこちを監視する必要があります。 今、あなたはあなたの人生を複雑にしないように定数に従う必要があります。 結局のところ、誰もそのようなコードから安全ではありません(そして、RVOに依存していて、このケースがあなたにとって重要ではないと思っても、 s構造にコピーコンストラクターがない場合、このコードがコンパイルされない理由を探すのに多くの時間を費やすことができます):

 struct s { ... }; s foo(); s bar() { const auto r = foo(); if (r.check_something()) throw std::exception(); return r; }
      
      





そして、このコードにもオブジェクトのコピーがあります(別の落とし穴):

 s bar() { auto&& r = foo(); ...; return r; }
      
      





このオプションを好む:

 s bar() { auto r = foo(); ...; return r; }
      
      





または、各移動コンストラクターを評価する場合のみ:

 s bar() { auto&& r = foo(); ...; return std::move(r); }
      
      





ご注意


前の例を、右辺値リンクを返す次のコードと混同しないでください。

 s&& bar() { auto&& r = foo(); ...; return std::move(r); }
      
      





これを実行しないでください。ここでは、すでに破壊されているオブジェクトへのリンクを返します。



そしてフォースがあなたと一緒にいるように!



All Articles