3
class C
{
public:
    int True(int i) const
    {
        return i+2;
    }
};


const C& F(const C& c)
{
    return c;
}

int main()
{ 
    const C& c = F(C());                             // Line 1
    cout << boolalpha << c.True(1) << endl;          // Line 2
}

質問> 上記のコードが正しい値を出力できるのはなぜですか? c変数が 2 行目に到達したときに、無効な一時Cオブジェクトを参照すると仮定します。

// アップデート

このOPを更新して、この質問に関心がある理由を説明したいと思います。

以下は、C++ テンプレートのコード スニペットです: The Complete Guide

// maximum of two values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b) 
{ 
    return a < b ? b : a; 
} 

ご覧のとおり、関数はパスイン パラメータへの参照を返します。代わりに次のバージョンではないのはなぜだろうか:

// maximum of two values of any type 
template <typename T> 
inline T max (T const& a, T const& b) 
{ 
    return a < b ? b : a; 
} 
4

4 に答える 4

5

C++11 標準のパラグラフ 12.2/4 では、状況によっては、一時変数の有効期間が、一時変数が生成された完全な式の終わりを超えて実際に延長される可能性があると指定されています。

完全な式の終わりとは異なる時点で一時的なものが破棄される状況が 2 つあります。[...]

最初のコンテキストは関係ありません。ただし、パラグラフ 12.2/5 によると:

2 番目のコンテキストは、参照がテンポラリにバインドされる場合です。参照がバインドされている一時オブジェクト、または参照がバインドされているサブオブジェクトの完全なオブジェクトである一時オブジェクトは、次を除いて、参照の存続期間中持続します

— コンストラクターの ctor-initializer (12.6.2) 内の参照メンバーへの一時的なバインドは、コンストラクターが終了するまで持続します。

関数呼び出し (5.2.2) の参照パラメーターへの一時的なバインドは、呼び出しを含む完全な式が完了するまで持続します。

— 関数 return ステートメント (6.6.3) の戻り値に一時的にバインドされているものの有効期間は延長されません。一時的なものは、return ステートメントの完全な式の最後で破棄されます。

— new-initializer (5.3.4) の参照への一時的なバインドは、new-initializer を含む完全な式が完了するまで持続します。

ここで、 で構築されたテンポラリは functionC()の引数にバインドされています。したがって、 function への呼び出しを含む完全式の最後で一時が破棄され、返される参照はdanglingになります。cFF()

関数を呼び出すTrue()と、Undefined Behaviorが発生します。

于 2013-02-20T20:39:15.873 に答える
3

通常、一時オブジェクトが const 参照にバインドされている場合、一時オブジェクトの有効期間は参照の有効期間まで延長されます。したがって、コードが言ったconst C& c = C()場合、一時的なものは存続cします。

ただし、一時的なものを別の関数に渡していますF()。この場合、C++11 仕様の §12.2.5 では、呼び出しを含む完全な式が完了するまで一時的なものは保持されると規定されています。

したがって、 と言うと、実際にはこのステートメントの最後でconst C& c = F(C())テンポラリC()が破棄され、次の行では有効ではなくなります。

つまり、 への呼び出しはコンパイル時に認識され、関数定義は実際には のデータを参照していないため、コードは適切に機能しているように見えますc.True()c. ただし、これは技術的に未定義の動作です。

于 2013-02-20T20:31:51.180 に答える
2

これにより、未定義の動作が発生します。を含むF(C())完全な式が完了するとすぐに、によって作成された一時C()は破棄されるため、によって返される参照はF無効になります。

ただし、未定義の動作は、プログラムがクラッシュすることを保証するものではありません。より厄介なケース (このようなもの) では、微妙で診断が難しいバグが発生するだけです。この未定義の動作がこの特定の結果をもたらす理由については、この有名な回答を参照してください。

于 2013-02-20T20:39:36.247 に答える
1

あなたが見ているのは、未定義の動作です。呼び出す関数はオブジェクトの状態や vtable にまったく依存しないため、コンパイラはそれをcout << boolalpha << ( 1+2 );にインライン化する可能性が非常に高く、オブジェクトが破棄されたかどうかは問題ではありません。実際、コンパイラはそうではない可能性があります。そもそもそれを作成することさえ気にしませんでした。

たとえば、VS2010 では、「デバッグ」で呼び出さFTrue、静的呼び出しとして呼び出されます。コードをTrue参照していないため、たまたま正常に動作します。(アクセスするthisメンバー変数がないため、たとえ機能したとしても機能する可能性があります。そのため、できることは のアドレスを出力することだけであり、それはスタック上のアドレスにすぎません。のデストラクタによって変更され、それらを使用したメンバー変数を使用すると、違いが見られます-すべての場合、動作は未定義であり、実装の単なるアーティファクトです)CthisCCTrue

C「リリース」では、VS2010 は、オブジェクトや呼び出しを作成することを気にしません。Fまたは、コンパイル時に の値を決定して、 をTrue呼び出すだけです。コンパイラが生成するプログラムには、無効であるかどうかにかかわらず、一時オブジェクトはありません。cout << boolalpha << 3C::True(2)C

したがって、オブジェクトの関数呼び出しが機能しているように見えるからといって、コンパイラによって生成されたプログラムにそのオブジェクトが存在する、または存在していたことを意味するわけではありません。未定義の動作が異なる別のソース プログラムにより、コンパイラは、アクセス違反を発生させる実行可能ファイルを生成したり、その他の動作を示したりする可能性があります。


戻り値を const 参照にバインドすることは、値による戻りに​​のみ適用され、パラメーターまたはローカルへの参照は返されません。それ以外の場合、コンパイラはオブジェクトのライフサイクルを決定するために停止問題を解決する必要があります。

たとえば、次のコード:

#include<iostream>

class C
{
public:
    int t;
    C( int t ) : t(t){}
    ~C() { std::cout << __FUNCTION__ << " " << t << std::endl; }
};

const C& F(const C& c)
{
    return c;
}
const C& G()
{
    return C(2);
}
C H()
{
    return C(3);
}

int main()
{
    const C& c = F(C(1));
    std::cout << "main 1" << std::endl;
    const C& d = G();    
    std::cout << "main 2" << std::endl;
    const C& e = H();    
    std::cout << "main 3" << std::endl;
}

この出力の結果 -H()値のみで返されるため、3 番目の C のみがライフサイクルが延長されます。

C::~C 1
main 1
C::~C 2
main 2
main 3
C::~C 3
于 2013-02-20T20:47:47.693 に答える