C ++ 11のreturnステートメントを使用した移動セマンティクスに関する注意

新しいC ++ 11標準をすぐにレビューして、右辺値参照のトピックについての理解を深めることにしました。 原理的にはすべてが素晴らしいですが、落とし穴があります。つまり、C ++ 03との後方互換性がいくらか失われます。



標準では、コンパイラはreturnステートメントに渡された式を右辺値参照と見なし、一時オブジェクトではない場合でも移動セマンティクスを実装することができます(ただし、義務付けはしません)。 例:

std :: 文字列 f

{

std :: string s = "Hi!" ;

return s ;

}


ここでは、return sの前に左辺値があり、returnはすでに右辺値参照として認識しています。 そしてこれは素晴らしい、なぜなら これにより、重いオブジェクトを返すときにパフォーマンスが向上します。コピーは発生しませんが、文字列の内部状態を移動します。 標準では、戻り値演算子が、右辺値参照のように、関数スタック領域に結果が存在する式を考慮することができる理由を理解してみましょうか? 答えは明らかです。はい。返された後、誰でもこの式の結果は不要になります。式が名前付きオブジェクトであっても、そのようなオブジェクトはxvalue(xPiring値)なので、安全に移動できます。



「もう必要ない」という言葉に注目することが重要です 。 戻り後、関数から戻る前にコードが呼び出されないことを保証することは可能ですか? Cでは可能ですが(SEHおよび__try ...__は最終的に標準ではありません)、C ++では不可能です。 returnが実行された後、関数が戻るまで、現在のスコープから始まり関数のスコープで終わる自動オブジェクトのデストラクタが呼び出されます。



//クラスは文字列への参照で初期化され、デストラクタはそれをコンソールに表示します。

struct finalizer

{

ファイナライザー std :: string const str

_str str

{

}



〜ファイナライザー

{

std :: cout << _str << std :: endl ;

}

プライベート

std :: string const _str ;

} ;



std :: 文字列 f

{

// NRVOを中断します(MSVC 10.0でのみチェックされます)

if false

return std :: string 「明らかに不可能なコード!」 ;

// -------------------------------------------



std :: string s = "Hi!" ;

ファイナライザーフィン;

return s ;

}


ダミーコードの最初の2行は、NRVO最適化を適用できないために必要です(MSVCの場合、検証用の手元に他のコンパイラはありません。お好みのコンパイラでNRVOを簡単にバイパスできます)。 C ++ 03コンパイラは、f関数が文字列「Hi!」を出力するコードを収集します。また、C ++ 11(誰もが既に推測したように)は空の文字列を出力します。



MSVC 9.0(c ++ 03)および10.0(部分的なc ++ 11)でテストを実施しました。 私の例では、C ++ 11コンパイラは理論的には「Hi!」という文字列も出力できます。 実際、ほとんどのコンパイラは短い文字列の保存の最適化を使用しています。 これを行うために、std ::文字列クラスはその内部に小さな固定バッファを格納します。文字列がその中に配置される場合、ヒープは使用されません。つまり、移動するものはなく、ドナー文字列は理論的には変更できず、さらに生産性が向上します。 この場合、ウェルカムラインを長くします。 短い行が出力され、長い行が出力されない場合、この動作は実行時にさらに予想外になります。 これは理論的にはです。 std :: stringの移動セマンティクスの実装に関する標準要件には気づかなかったが、ドナー文字列は事後条件s.empty()== trueを満たす必要がありますか? そのようなことがあれば、私を修正してください。





この例は手に負えませんが、簡単に検討できます。 実際には、コードを新しい標準に移行するような問題に遭遇したことはありません。 リスクグループは、BOOST_SCOPE_EXITを使用するコード(便利なことで、何度か使用する必要があります)であるとしか想定できません。これは、正確に、クラスデストラクタを呼び出すことによって最終コードを実行することを目的としています。 また、手書きのファイナライザを見ることができました。



いずれにせよ、これは生産性のかなり強力な増加を支持する下位互換性のある種の犠牲です。 ここでは、失うものよりもはるかに多くのものを獲得しています。



この記事がそのような状況を予測し、不必要な神経からそれを捕まえた潜在的な幸運な人を救い、グリッチを探して新しいコンパイラに切り替えるときに頭の余分な髪をいくつか救うことを願っています。



C ++ 11にうまく移行できます。



UPD:例にダミーコードが含まれているため、MSVCのNRVOをバイパスできます。



All Articles