2016-12-04 1 views
47

Visual StudioとGCCの両方で次のコードがクラッシュするのはなぜですか?なぜイニシャライザの範囲ベースの一時オブジェクトを使用するとクラッシュするのですか?

クラッシュするには、範囲ベースのforループ、std :: map、std :: stringが必要で、文字列への参照が必要です。もし私がそれらのいずれかを削除すると、それは動作します。 http://ideone.com/IBmhDH

+0

OS XでClangとうまく動作します。使用しているGCCのバージョンと使用しているプラ​​ットフォームは何ですか? –

+0

技術的には、あなたが 'find'を任意に使用する前に成功したことをテストするべきです。 'm.end()'と比較するか 'm [" key "]'を使ってください。 – tadman

+3

@tadman:キーが存在することがわかっているかどうかをテストする必要はありません。 –

答えて

61

for(:)ループの範囲の初期化行は、の最後の(存在する場合)以外のものの有効期間を延長しません。その他の一時的なものはループ実行前に破棄されます。

今、絶望しないでください。この問題は簡単に修正できます。しかしまず、何がうまくいかないのかを見てみましょう。

コードfor(auto x:exp){ /* code */ }は基本的には、次のように拡張されます

{ 
    auto&& __range=exp; 
    auto __it=std::begin(__range); 
    auto __end=std::end(__range); 
    for(; __it!=__end;++__it){ 
    auto x=*__it; 
    /* code */ 
    } 
} 

__it__endライン上のささやかな嘘、そして__で始まるすべての変数は可視名前を持っていないとも私はC++ 17を示しています。バージョンは、より良い世界を信じているので、違いはここでは関係ありません)

expexpは、一時的なオブジェクトを作成し、そこに参照を返します。その行の後に一時的に死んでしまうので、残りのコードにはダングリングリファレンスがあります。

固定は比較的簡単です。それを修正するには:

std::string const& func() const& // notice & 
{ 
    return m.find("key")->second; 
} 
std::string func() && // notice && 
{ 
    return std::move(m.find("key")->second); 
} 

は右辺値オーバーロードを行うと、代わりにそれらに参照を返すの一時を消費したときに値によって移動-に値をを返します。

そして

auto&& __range=exp; 

線はバイ値に基準長寿命化がstring、そしてこれ以上ダングリング参照を返しません。

一般的に、rvalueになる可能性のあるパラメータを参照して範囲を返すことはありません。


付録:、メソッドの後&&const&を待って? rvalue references to *this

C++ 11にはrvalue referencesが追加されました。しかし、関数へのthisまたは自己パラメータは特別です。呼び出されるオブジェクトの右辺値または左辺値に基づいてメソッドのオーバーロードを選択するには、メソッドの終了後に&または&&を使用できます。

これは、関数のパラメータの型によく似ています。 &&メソッドは、メソッドが非const rvaluesでのみ呼び出されるべきであると述べた後、 const&は、定数lvaluesに対して呼び出される必要があることを意味します。正確に一致しないものは、通常の序数規則に従います。

オブジェクトへの参照を返すメソッドがある場合は、&&オーバーロードで一時的なものをキャッチし、それらの場合は参照を返しません(値を返します)。=deleteメソッドを返します。

+0

範囲ベースの 'for'ループの生涯の問題に対処するDRはありませんでしたか? – Morwenn

+0

@Morwenn:この組み合わせ(パラメータとして渡された一時的なものを 'this'でも返す)は、どのコンテキストでも生涯の問題を引き起こし、ranged-forは関連しません。 –

+0

@catここで、未定義の動作がクラッシュするように定義されているという考えを得ましたか? Morwennは、地域のDRを認識していると主張していますが、私はそれを知らないのです。 – Yakk

30
S().func() 

これは一時的なオブジェクトを作成し、一時オブジェクトによって(間接的に)所有いstd::stringへの参照を返すメソッドを呼び出す(std::stringはである:リンクは

#include <iostream> 
#include <string> 
#include <map> 
using namespace std; 

struct S 
{ 
    map<string, string> m; 

    S() 
    { 
     m["key"] = "b"; 
    } 

    const string &func() const 
    { 
     return m.find("key")->second; 
    } 
}; 

int main() 
{ 
    for (char c : S().func()) 
     cout << c; 

    return 0; 
} 

Ideone一時オブジェクトの一部であるコンテナ)。

参照を取得した後、一時オブジェクトは破棄されます。これにより、一時オブジェクトによって(間接的に)所有されたstd::stringも破棄されます。

この後、参照されるオブジェクトのそれ以上の使用法は未定義の動作になります。その内容を反復するなど。

範囲の繰り返しを使用する場合、これは非常に一般的な落とし穴です。あなたは本当にこれを乗り越えて罪を犯しています。

+1

'const T&'を返さない**についての規則はありますか?それとも、複雑なクラスメンバーにとっては、通常は効率的ですが、イテレーターを壊すだけですか? –

+0

これは面白いです。私はなぜ一時的なオブジェクトが破壊されるのだろうか?私がcout << S()。func()[0];一時的なSが破壊されることはありませんか?レンジベースのforループが異なるのは何ですか? – kynnysmatto

+1

一時表記は式の最後に破棄されるためです。 'cout << ...;'の場合、セミコロンは式の終わりです。範囲の繰り返しの場合は、 ')'(大体言い換えれば)です。また、表現の途中で一時的なものが破壊される状況もありますが、ここにもそこにはありません。 –

関連する問題