C ++ローカル変数への参照を返す


116

次のコード(func1())は、iを返す必要がある場合、正しいですか?ローカル変数への参照を返すときに問題があることをどこかで読んだことを覚えています。func2()とどう違うのですか?

int& func1()
{
    int i;
    i = 1;
    return i;
}

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

1
動的に割り当てられたメモリを使用するようにfunc1()を変更した場合、それらは同じです:-)int& i = * new int;
Martin York

回答:


193

このコードスニペット:

int& func1()
{
    int i;
    i = 1;
    return i;
}

関数呼び出しのスコープに制限された存続期間を持つオブジェクトにエイリアス(参照)を返すため、機能しません。これは、一度func1()戻るとint i死に、存在しないオブジェクトを参照するため、関数から返された参照が無意味になることを意味します。

int main()
{
    int& p = func1();
    /* p is garbage */
}

2番目のバージョンは、変数がフリーストアに割り当てられ、関数呼び出しの存続期間にバインドされていないため、機能します。ただし、delete割り当てられたを使用する必要がありますint

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

int main()
{
    int* p = func2();
    /* pointee still exists */
    delete p; // get rid of it
}

通常、ポインタをRAIIクラスやファクトリ関数でラップするのでdelete、自分でそれを行う必要はありません。

どちらの場合でも、値自体を返すことができます(提供した例はおそらく不自然なものであると思いますが)。

int func3()
{
    return 1;
}

int main()
{
    int v = func3();
    // do whatever you want with the returned value
}

func3()現在ほとんどすべてのコンパイラーが何らかの形の戻り値の最適化を実装しているため、プリミティブ値を返すのと同じ方法で大きなオブジェクトを返すことは完全に問題ありません。

class big_object 
{ 
public:
    big_object(/* constructor arguments */);
    ~big_object();
    big_object(const big_object& rhs);
    big_object& operator=(const big_object& rhs);
    /* public methods */
private:
    /* data members */
};

big_object func4()
{
    return big_object(/* constructor arguments */);
}

int main()
{
     // no copy is actually made, if your compiler supports RVO
    big_object o = func4();    
}

興味深いことに、テンポラリをconst参照にバインドすることは完全に正当なC ++です。

int main()
{
    // This works! The returned temporary will last as long as the reference exists
    const big_object& o = func4();    
    // This does *not* work! It's not legal C++ because reference is not const.
    // big_object& o = func4();  
}

2
美しい説明。:hattip:3番目のコードスニペットでは、削除していint* p = func2(); delete p;ます。「p」を削除すると、関数func2()の定義の「内部」に割り当てられたメモリも削除されたということですか。
Aquarius_Girl

2
@アニシャ・カウル:はい。メモリは内部に割り当てられfunc2()、次の行で外部に解放されました。ただし、メモリを処理するためにエラーが発生しやすい方法です。代わりに、RAIIのバリアントを代わりに使用するとしました。ところで、あなたはC ++を学んでいるように聞こえます。学ぶために、C ++の入門書を手に入れることをお勧めします。また、質問がある場合の参考のために、いつでもStack Overflowに質問を投稿できます。コメントは、まったく新しい質問をするためのものではありません。
インシリコ

今私は理解しました、あなたはそれを正しく行いました!関数がポインターを返していましたが、その関数の外で、それが指しているメモリを削除しました。今では明らかであり、リンクをありがとう。
Aquarius_Girl

そしてあなたは答えを編集しましたか?:mad:簡単に見逃していたかもしれません。;);)
Aquarius_Girl

@Anisha Kaul:いいえ、しませんでした。投稿の下のタイムスタンプによると、私が最後に回答を編集したのは1月10日でした。
インシリコ

18

ローカル変数はスタック上のメモリであり、スコープ外に出てもそのメモリは自動的に無効にはなりません。深くネストされた関数(メモリのスタックの上位)から、このメモリにアクセスすることは完全に安全です。

関数が戻って終了すると、事態は危険になります。通常、メモリは戻ったときに削除も上書きもされません。つまり、そのアドレスのメモリにはまだデータが含まれています。ポインタは有効なようです。

別の関数がスタックを構築して上書きするまで。これがしばらくの間機能する理由です-そして、特に深くネストされた関数のセット、または本当に巨大なサイズまたは多くのローカルオブジェクトを持つ関数が再びそのスタックメモリに到達した後、突然機能を停止します。

同じプログラム部分に再び到達し、古いローカル関数変数を新しい関数変数で上書きすることも起こり得ます。これはすべて非常に危険であり、あまりお勧めできません。 ローカルオブジェクトへのポインタは使用しないでください。


2

覚えておくと良いのは、これらの単純なルールであり、それらはパラメーターと戻り値の型の両方に適用されます...

  • 値-問題のアイテムのコピーを作成します。
  • ポインタ-問題のアイテムのアドレスを指します。
  • 参照-文字通り問題のアイテムです。

それぞれに時間と場所があるので、それらを知るようにしてください。ここで示したように、ローカル変数はそれだけであり、関数スコープ内でローカルに生きている時間に制限されます。あなたの例では、戻り値の型int*と戻り値&iが同じように正しくないでしょう。あなたはその場合これを行う方が良いでしょう...

void func1(int& oValue)
{
    oValue = 1;
}

そうすると、渡されたパラメータの値が直接変更されます。一方、このコードは...

void func1(int oValue)
{
    oValue = 1;
}

しません。oValue関数呼び出しのローカルの値を変更するだけです。これは、実際にはの「ローカル」コピーだけを変更するためoValueであり、oValueそれ自体を変更することはないためです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.