C ++参照変数を返す習慣は悪ですか?


341

これは少し主観的なものだと思います。意見が全会一致になるかどうかはわかりません(参照が返されるコードスニペットをたくさん見ました)。

この質問に対するコメントによると、参照の初期化に関して参照を返すことは悪いことです。なぜなら、[理解しているように]参照を削除するのを忘れやすく、メモリリークにつながる可能性があるためです。

これは、私が例を考えて(私が物事を想像しているのでない限り)、かなりの数の場所でこれを行ったので心配です...誤解していませんか?悪か?もしそうなら、どれだけ悪か?

ポインターと参照の混合バッグと、C ++を初めて使用するという事実、および何をいつ使用するかについての完全な混乱のため、アプリケーションはメモリリークの地獄である必要があると思います...

また、メモリリークを回避する最良の方法として、スマート/共有ポインターの使用が一般的に受け入れられていることも理解しています。


あなたがゲッターのような関数/メソッドを書いているのは悪いことではありません。
ジョンZ.リー

回答:


411

一般に、参照を返すことは完全に正常であり、常に発生します。

つまり:

int& getInt() {
    int i;
    return i;  // DON'T DO THIS.
}

それはあらゆる種類の悪です。スタック割り当てiはなくなり、あなたは何も参照していません。これも悪です:

int& getInt() {
    int* i = new int;
    return *i;  // DON'T DO THIS.
}

今やクライアントは最終的に奇妙なことをしなければならないので:

int& myInt = getInt(); // note the &, we cannot lose this reference!
delete &myInt;         // must delete...totally weird and  evil

int oops = getInt(); 
delete &oops; // undefined behavior, we're wrongly deleting a copy, not the original

右辺値の参照はまだ単なる参照であるため、すべての邪悪なアプリケーションは同じままです。

関数のスコープを超えて存在するものを割り当てる場合は、スマートポインター(または一般にコンテナー)を使用します。

std::unique_ptr<int> getInt() {
    return std::make_unique<int>(0);
}

そして今、クライアントはスマートポインタを保存します:

std::unique_ptr<int> x = getInt();

参照は、存続期間が上位レベルで開いたままになっていることがわかっているものにアクセスする場合にも問題ありません。たとえば、次のようになります。

struct immutableint {
    immutableint(int i) : i_(i) {}

    const int& get() const { return i_; }
private:
    int i_;
};

ここでは、参照を返すことが問題ないことを知っていi_ます。これi_は、呼び出しているものがクラスインスタンスの存続期間を管理するため、少なくともその期間は存続するためです。

そしてもちろん、次のことだけで問題はありません。

int getInt() {
   return 0;
}

存続期間を呼び出し元に任せるべきであり、値を計算しているだけの場合。

概要:呼び出し後にオブジェクトの存続期間が終了しない場合は、参照を返しても問題ありません。


21
これらはすべて悪い例です。適切な使用法の最良の例は、渡されたオブジェクトへの参照を返す場合です
。ala

171
後世のために、そしてこれを追いかける新しいプログラマーにとって、ポインタは悪くありません。動的メモリへのポインタも不良ではありません。どちらもC ++で正当な場所にあります。動的メモリ管理に関しては、スマートポインタは必ずデフォルトの推奨事項ですが、デフォルトのスマートポインタは、shared_ptrではなくunique_ptrにする必要があります。
Jamin Grey

12
承認者の編集:正当性を保証できない場合は編集を承認しないでください。間違った編集をロールバックしました。
GManNickG 2014年

7
後世のために、そしてこれを追いかける新しいプログラマーのために、書いてreturn new intいけません
オービットでの軽さのレース2015年

3
後世のために、そしてこれを追いかける新しいプログラマーのために、関数からTを返すだけです。RVOがすべてを処理します。
2015

64

いいえ、いいえ、千回はありません。

悪いことは、動的に割り当てられたオブジェクトへの参照を作成し、元のポインタを失うことです。あなたnewがオブジェクトであるとき、あなたは保証される義務を負いますdelete

しかし、例えば、見てoperator<<いる:しなければならないの参照を返しますか、

cout << "foo" << "bar" << "bletch" << endl ;

動作しません。


23
これは(OPが削除の必要性を知っていることをOPが明らかにした)質問に答えず、freestoreオブジェクトへの参照を返すことが混乱を招く可能性があるという正当な恐れにも対処していないため、私は反対票を投じました。はぁ。

4
参照オブジェクトを返すことは悪いことではありません。エルゴいいえ。2番目のグラフで指摘したように、彼が表現する恐怖は正しい恐怖です。
チャーリーマーティン

あなたは実際にはしませんでした。しかし、これは私の時間の価値はありません。

2
Iraimbilanja @「いいえ」について私は気にしません。しかし、この投稿はGManから欠落していた重要な情報を指摘しました。
Kobor42 2013年

48

すぐになくなるわけではなく、所有権の譲渡を意図しない既存のオブジェクトへの参照を返す必要があります。

ローカル変数などへの参照を返さないでください。参照されることはないからです。

関数から独立した何かへの参照を返すことができます。これは、呼び出し側の関数が削除の責任を負うことを期待していません。これは典型的なoperator[]機能の場合です。

何かを作成している場合は、値またはポインター(通常またはスマート)を返す必要があります。値は呼び出し元の関数の変数または式に入るので、自由に返すことができます。ローカル変数へのポインタは戻されないため、決して戻さないでください。


1
すばらしい答えですが、「一時参照をconst参照として返すことができます」。次のコードはコンパイルされますが、一時ステートメントがreturnステートメントの最後に破棄されるため、おそらくクラッシュします: "int const&f(){return 42;} void main(){int const&r = f(); ++ r;} "
j_random_hacker 2009

@j_random_hacker:C ++には、一時的な有効期間が延長される可能性のある一時的なものへの参照に関するいくつかの奇妙な規則があります。申し訳ありませんが、あなたのケースをカバーしているかどうかはわかりません。
マークランサム

3
@マーク:はい、奇妙なルールがあります。テンポラリの寿命は、それを使ってconst参照(クラスメンバーではない)を初期化することによってのみ延長できます。その後、参照が範囲外になるまで存続します。残念ながら、const refを返すことカバーされていません。ただし、tempを値で返すのは安全です。
j_random_hacker 2009

C ++標準、12.2、パラグラフ5を参照してください。herbsutter.wordpress.com/ 2008/01/01 /…にある Herb Sutterの今週の迷った達人も参照してください。
David Thornley、

4
@David:関数の戻り値の型が「T const&」の場合、実際に起こるのは、returnステートメントが暗黙的に T型のtempを6.6.3.2に従って型「T const&」に変換することです(正当な変換では1これにより、存続期間は延長されません)。その後、呼び出しコードは、「T const&」タイプのrefを、「T const&」タイプの関数の結果で初期化します。これも、有効な、ただし有効期間を延長しないプロセスです。最終結果:寿命の延長はなく、多くの混乱。:(
j_random_hacker

26

答えが満足のいくものではないので、2セント追加します。

次のケースを分析してみましょう。

誤った使い方

int& getInt()
{
    int x = 4;
    return x;
}

これは明らかにエラーです

int& x = getInt(); // will refer to garbage

静的変数での使用

int& getInt()
{
   static int x = 4;
   return x;
}

静的変数はプログラムの存続期間を通じて存在するため、これは正しいです。

int& x = getInt(); // valid reference, x = 4

これは、シングルトンパターンを実装する場合にも非常に一般的です

Class Singleton
{
    public:
        static Singleton& instance()
        {
            static Singleton instance;
            return instance;
        };

        void printHello()
        {
             printf("Hello");
        };

}

使用法:

 Singleton& my_sing = Singleton::instance(); // Valid Singleton instance
 my_sing.printHello();  // "Hello"

オペレーター

標準ライブラリコンテナは、たとえば、参照を返す演算子の使用に大きく依存します。

T & operator*();

以下で使用できます

std::vector<int> x = {1, 2, 3}; // create vector with 3 elements
std::vector<int>::iterator iter = x.begin(); // iterator points to first element (1)
*iter = 2; // modify first element, x = {2, 2, 3} now

内部データへの迅速なアクセス

内部データにすばやくアクセスするために&が使用される場合があります

Class Container
{
    private:
        std::vector<int> m_data;

    public:
        std::vector<int>& data()
        {
             return m_data;
        }
}

使用法:

Container cont;
cont.data().push_back(1); // appends element to std::vector<int>
cont.data()[0] // 1

ただし、これは次のような落とし穴につながる可能性があります。

Container* cont = new Container;
std::vector<int>& cont_data = cont->data();
cont_data.push_back(1);
delete cont; // This is bad, because we still have a dangling reference to its internal data!
cont_data[0]; // dangling reference!

静的変数への参照を返すことは、静的メンバへの参照を返す乗算演算子を検討望ましくない行動などにつながることができ、その後、次はいつもになりますtrueIf((a*b) == (c*d))
SebNag

Container::data()の実装を読む必要がありますreturn m_data;
Xeaz

これはとても役に立ちました、ありがとう!@Xeazは追加呼び出しで問題を引き起こしませんか?
Andrew

@SebTuどうしてそんなことしたいの?
thorhunter 2017

@Andrewいいえ、それは構文の不正解でした。たとえば、ポインタ型を返した場合、参照アドレスを使用してポインタを作成して返します。
thorhunter 2017

14

それは悪ではありません。C ++での多くのことと同様に、正しく使用することは良いことですが、それを使用するときに(ローカル変数への参照を返すなど)注意すべき多くの落とし穴があります。

それで達成できる良いものがあります(map [name] = "hello world"など)


1
気になるだけなんだけど、何がいいのmap[name] = "hello world"
11

5
@wrongusername構文は直感的です。HashMap<String,Integer>Javaに格納されている値のカウントをインクリメントしようとしたことがありますか?:P
Mehrdad Afshari、2011年

ハハ、まだではありませんが、HashMapの例を見ると、非常に危険に見えます:D
wrongusername '20年

これで私が抱えていた問題:関数はコンテナー内のオブジェクトへの参照を返しますが、呼び出し元の関数コードはそれをローカル変数に割り当てました。次に、オブジェクトのいくつかのプロパティを変更しました。問題:コンテナ内の元のオブジェクトがそのまま残りました。プログラマーは戻り値の&を簡単に見落とすので、本当に予期しない動作が発生します...
flohack

10

「参照を返すのは悪いことです。なぜなら、単純に(私が理解しているように)参照を削除するのを忘れがちだからです」

違います。参照を返すことは、所有権のセマンティクスを意味しません。つまり、これを行うからといって:

Value& v = thing->getTheValue();

... vが参照するメモリを現在所有しているという意味ではありません。

ただし、これは恐ろしいコードです。

int& getTheValue()
{
   return *new int;
}

「そのインスタンスにポインタは必要ない」ためにこのようなことをしている場合:1)参照が必要な場合はポインタを逆参照するだけであり、2)最終的にポインタが必要になります。削除付きの新規、および削除を呼び出すためのポインタが必要です。


7

2つのケースがあります。

  • constリファレンス-良いアイデア、時には、特に重いオブジェクトやプロキシクラスの場合、コンパイラの最適化

  • 非const参照-悪い考えは、時々、カプセル化を壊します

どちらも同じ問題を共有しています-破壊されたオブジェクトを指す可能性があります...

参照/ポインターを返す必要がある多くの状況でスマートポインターを使用することをお勧めします。

また、次の点にも注意してください。

正式な規則があります-C ++標準(興味がある場合はセクション13.3.3.1.4)は、一時変数はconst参照にのみバインドできると述べています-非const参照を使用しようとすると、コンパイラーはこれにフラグを立てる必要がありますエラー。


1
non-const refは必ずしもカプセル化を壊すわけではありません。vector :: operator []を検討してください

それは非常に特別なケースです...だから私は時々言ったのですが、私は本当に最も時間を主張する必要があります:)

それで、あなたは通常の添え字演算子の実装が必要な悪だと言っているのですか?私はこれに同意も同意もしません。私は賢明ではないので。
ニックボルトン

私はそれを言っていませんが、誤用されればそれは悪になる可能性があります:)))vector :: atは可能な限り使用する必要があります...

え?vector :: atは、非const参照も返します。

4

それは悪ではないだけでなく、時には不可欠です。たとえば、参照戻り値を使用せずにstd :: vectorの[]演算子を実装することは不可能です。


もちろん、はい。これが私がそれを使い始めた理由だと思います。最初に添え字演算子[]を実装したときのように、参照の使用に気付きました。これが事実であると私は信じています。
ニックボルトン

奇妙なことに、あなたが実装することができoperator[]、参照を使用せずに、コンテナのため...とstd::vector<bool>ありません。(そして、プロセスで実際の混乱を作成します)
Ben Voigt 2017

@BenVoigt mmm、なぜ混乱するの?プロキシを返すことは、(::std::vector<bool>前述のように)外部の型に直接マッピングされない複雑なストレージを持つコンテナの有効なシナリオでもあります。
Sergey.quixoticaxis.Ivanov 2017年

1
@ Sergey.quixoticaxis.Ivanov:他のインスタンス化とは非常に異なる動作をするためstd::vector<T>、テンプレートコードでの使用が壊れているT可能性がboolありstd::vector<bool>ます。便利ですが、の特殊化ではなく、独自の名前を付ける必要がありstd::vectorます。
Ben Voigt 2017年

@BenVoight私はある専門を「本当に特別」にするという奇妙な決定についての点で同意しますが、あなたの元のコメントはプロキシを返すことは一般的に奇妙であると示唆していると感じました。
Sergey.quixoticaxis.Ivanov 2017年

2

受け入れられた回答への追加:

struct immutableint {
    immutableint(int i) : i_(i) {}

    const int& get() const { return i_; }
private:
    int i_;
};

この例は大丈夫ではないので、できれば避けるべきと私は主張します。どうして?ぶら下がっている参照で終わることは非常に簡単です。

例でポイントを説明するには:

struct Foo
{
    Foo(int i = 42) : boo_(i) {}
    immutableint boo()
    {
        return boo_;
    }  
private:
    immutableint boo_;
};

危険ゾーンに入る:

Foo foo;
const int& dangling = foo.boo().get(); // dangling reference!

1

値を返すにはコピー操作が必要なため、戻り参照は通常、C ++の演算子オーバーロードで使用されます(peratorオーバーロードでは、通常、ポインターを戻り値として使用しません)。

ただし、参照を返すと、メモリ割り当ての問題が発生する可能性があります。結果への参照は戻り値への参照として関数から渡されるため、戻り値を自動変数にすることはできません。

戻り参照を使用する場合は、静的オブジェクトのバッファを使用できます。例えば

const max_tmp=5; 
Obj& get_tmp()
{
 static int buf=0;
 static Obj Buf[max_tmp];
  if(buf==max_tmp) buf=0;
  return Buf[buf++];
}
Obj& operator+(const Obj& o1, const Obj& o1)
{
 Obj& res=get_tmp();
 // +operation
  return res;
 }

このようにして、安全に参照を返すことができます。

ただし、関数の値を返すために、参照ではなくポインタを常に使用できます。


0

関数の戻り値として参照を使用することは、関数の戻り値としてポインタを使用するよりもはるかに簡単だと思います。第二に、戻り値が参照する静的変数を使用することは常に安全です。


0

最善の方法は、オブジェクトを作成し、この変数を割り当てる関数に参照/ポインタパラメータとして渡すことです。

関数ブロックの最後でメモリを解放するため、オブジェクトを関数に割り当て、それを参照またはポインタ(ただし、ポインタの方が安全です)として返すことはお勧めできません。


-1
    Class Set {
    int *ptr;
    int size;

    public: 
    Set(){
     size =0;
         }

     Set(int size) {
      this->size = size;
      ptr = new int [size];
     }

    int& getPtr(int i) {
     return ptr[i];  // bad practice 
     }
  };

getPtr関数は、削除後に動的メモリまたはnullオブジェクトにアクセスできます。これにより、不正アクセス例外が発生する可能性があります。代わりに、getterとsetterを実装し、サイズを確認してから戻ります。


-2

lvalueとしての関数(別名、非const参照の戻り)は、C ++から削除する必要があります。それはひどく直感的ではありません。スコットマイヤーズ氏は、この動作を備えたmin()を望んでいました。

min(a,b) = 0;  // What???

これは本当に改善されていません

setmin (a, b, 0);

後者はもっと理にかなっています。

lvalueとしての機能はC ++スタイルのストリームにとって重要であることを理解していますが、C ++スタイルのストリームはひどいことを指摘する価値があります。私がこれを考えるのは私だけではありません... Alexandrescuがより良い方法についての大きな記事を持っていたことを思い出します。


2
確かにそれは危険であり、より良いコンパイラエラーチェックがあるはずですが、それがないといくつかの便利な構成を実行できません。たとえば、std :: mapのoperator []()
j_random_hacker

2
非const参照を返すことは、実際には非常に便利です。vector::operator[]例えば。どちらを書くv.setAt(i, x)v[i] = x?後者はFARに優れています。
Miles Rout 2014年

1
@MilesRout v.setAt(i, x)いつでも行きます。FAR優れています。
2018

-2

私はそれが実際に悪であるという本当の問題に遭遇しました。基本的に、開発者はベクター内のオブジェクトへの参照を返しました。それは悪かった!

私がJanuraryで書いた完全な詳細:http ://developer-resource.blogspot.com/2009/01/pros-and-cons-of-returing-references.html


2
呼び出しコードの元の値を変更する必要がある場合は、refを返す必要があります。そしてそれは実際にはイテレータをベクトルに返すことと同じくらい危険ではありません-要素がベクトルに追加またはベクトルから削除されると両方とも無効になります。
j_random_hacker 2009

その特定の問題は、ベクター要素への参照を保持し、その参照を無効にする方法でそのベクターを変更したために発生しました。または要素の無効化を参照し、ポインタ、次の要素を参照する反復子を削除、挿入が再割り当てが発生した場合、それはすべての参照、反復子、およびポインタ」が無効になります。
トレント

-15

恐ろしいコードについて:

int& getTheValue()
{
   return *new int;
}

したがって、実際には、メモリポインタは復帰後に失われました。しかし、そのようなshared_ptrを使用する場合:

int& getTheValue()
{
   std::shared_ptr<int> p(new int);
   return *p->get();
}

返却後もメモリは失われず、割り当て後に解放されます。


12
共有ポインタがスコープ外になり、整数を解放するため、失われます。

ポインタは失われません。参照のアドレスはポインタです。
dgsomerton 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.