メンバーデータでポインターまたは参照を優先する必要がありますか?


138

これは質問を説明するための単純化された例です:

class A {};

class B
{
    B(A& a) : a(a) {}
    A& a;
};

class C
{
    C() : b(a) {} 
    A a;
    B b; 
};

したがって、BはCの一部を更新する責任があります。私はlintを介してコードを実行し、それは参照メンバーlint#1725を使用していました。これは、十分に公正なデフォルトのコピーと割り当てを処理することについて話しますが、デフォルトのコピーと割り当てもポインターに関して悪いので、そこにはほとんど利点がありません。

ネイキッドポインターは誰がそのポインターを削除する責任があるかについて不確実に導入するため、私は常に可能な限り参照を使用するようにしています。オブジェクトを値で埋め込むことを好みますが、ポインターが必要な場合は、ポインターを所有するクラスのメンバーデータでauto_ptrを使用し、オブジェクトを参照として渡します。

通常、メンバーデータでポインターを使用するのは、ポインターがnullになるか変更される可能性がある場合のみです。データメンバーの参照よりもポインターを優先する他の理由はありますか?

一度初期化されたリファレンスは変更されるべきではないので、リファレンスを含むオブジェクトは割り当て可能であるべきではないというのは本当ですか?


「しかし、デフォルトのコピーと割り当てもポインタでは良くありません」:これは同じではありません。ポインタがconstでなければ、いつでも変更できます。参照は通常常にconstです!。。でも「CONST」キーワードを指定せずに参照がとにかくconstのあることを警告します)以上GCCのコンパイラを(;「A&CONST」(あなたはあなたがあなたのメンバーを変更しようとした場合、これを表示されます
MMMMMMMM

これの主な問題は、誰かがB b(A())を実行すると、ぶら下がっている参照になってしまうので、あなたはねじ込まれているということです。
log0

回答:


68

参照メンバーは、クラスの実装が実行できることを制限し(言及したように、代入演算子の実装の防止を含む)、クラスが提供できるものに利点を提供しないため、避けてください。

問題の例:

  • 各コンストラクターの初期化リストで参照を初期化する必要があります。この初期化を別の関数に分解する 方法はありません(C ++ 0xまで、とにかく編集: C ++には委任コンストラクターがあります
  • 参照を再バインドしたり、nullにすることはできません。これは利点になる可能性がありますが、コードを変更して再バインドを許可したり、メンバーをnullにしたりする必要がある場合、メンバーのすべての使用を変更する必要があります。
  • ポインターメンバーとは異なり、リファクタリングが必要な場合があるため、参照をスマートポインターまたはイテレーターで簡単に置き換えることはできません。
  • 参照が使用されるときはいつでも、それは値の型(.演算子など)のように見えますが、ポインタのように動作します(ぶら下がることができます)。たとえば、Googleスタイルガイドでは推奨されません。

65
あなたが言及したすべてのことは避けるべき良いことなので、もし参照がこれに役立つなら-それらは良いことであり、悪くはありません。初期化リストは、データを初期化するのに最適な場所です。多くの場合、必要のない参照を使用して、代入演算子を非表示にする必要があります。「リバウンドできません」-変数を再利用するのはよくありません。
Mykola Golubyev、2009年

4
@Mykola:私はあなたに同意します。私はメンバーをイニシャライザリストで初期化することを好みます。ヌルポインタは避け、変数がその意味を変更するのは確かに良くありません。私たちの意見が異なるのは、コンパイラーがこれを強制する必要がない、または望まないということです。クラスを作成するのが簡単になるわけではありません。この領域でバグが発生することはありません。また、ポインターメンバー(適切な場合はスマートポインター)を一貫して使用するコードから得られる柔軟性に感謝します。
Jamesホプキン、

割り当てを非表示にする必要がないのは偽の引数だと思います。クラスに削除の責任がないポインターが含まれている場合は、割り当てを非表示にする必要がないためです。これが最良の答えだと思います。メンバーデータ内の参照よりもポインターを優先するべきであるという正当な理由が得られるからです。
markh44 2009年

4
ジェームズ、それはコードを書きやすくすることではなく、コードを読みやすくすることです。参照をデータメンバーとして使用すると、使用時にnullになる可能性があるかどうかを気にする必要はありません。つまり、必要なコンテキストを減らしてコードを見ることができます。
Len Holgate、2010年

4
それはユニットテストとモックアウトをはるかに難しくし、使用数によってはほとんど不可能であり、「影響を受けるグラフの爆発」と相まってIMHOはそれらを使用しない十分な理由を説得します。
Chris Huang-Leaver、2011年

155

私自身の経験則:

  • オブジェクトの存続期間を他のオブジェクトの存続期間に依存させる場合は、参照メンバーを使用します。これは、オブジェクトが他のクラスの有効なインスタンスなしでは存続できないことを明示的に示す方法ですコンストラクターを介して参照の初期化を取得する割り当てと義務。これは、インスタンスがメンバーである、または別のクラスのメンバーではないことを想定せずにクラスを設計するための良い方法です。あなたは彼らの生活が他のインスタンスに直接リンクされていると仮定するだけです。これにより、後でクラスインスタンスの使用方法を変更できます(新しい、ローカルインスタンス、クラスメンバー、マネージャーのメモリプールなどによって生成されます)。
  • その他の場合はポインターを使用する:メンバーを後で変更する場合は、ポインターまたはconstポインターを使用して、ポイントされたインスタンスのみを読み取るようにします。そのタイプがコピー可能であると想定されている場合、とにかく参照を使用することはできません。特別な関数呼び出し(たとえばinit())の後でメンバーを初期化する必要がある場合もあり、その場合はポインタを使用する以外に選択肢がありません。しかし、すべてのメンバー関数でアサートを使用して、間違ったポインター状態をすばやく検出します!
  • オブジェクトの存続期間を外部オブジェクトの存続期間に依存させる必要があり、そのタイプもコピー可能にする必要がある場合は、ポインターメンバーを使用しますが、コンストラクターで参照引数を使用します。このようにして、このオブジェクトの存続期間が依存することを構築に示します。引数の有効期間については、実装は引き続きポインタを使用してコピー可能です。これらのメンバーがコピーによってのみ変更され、型にデフォルトのコンストラクターがない限り、型は両方の目標を満たす必要があります。

1
私はあなたとMykolaが正しい答えであり、ポインタのない(ポインタが少ない)プログラミングを促進すると思います。
amit

37

オブジェクトは、割り当てや比較などの他のものを許可することはめったにありません。「Department」、「Employee」、「Director」などのオブジェクトを含むビジネスモデルを検討する場合、ある従業員が別の従業員に割り当てられるケースを想像するのは困難です。

したがって、ビジネスオブジェクトの場合、1対1および1対多の関係を、ポインタではなく参照として記述することは非常に良いことです。

そしておそらく、1または0の関係をポインタとして記述しても問題ありません。

したがって、「割り当てることはできません」ではなく、因数分解します。
多くのプログラマーはポインターに慣れているだけなので、参照の使用を避けるために引数を見つけます。

メンバーとしてポインターを使用すると、「念のため」のコメントを使用して、使用前に何度もポインターを確認する必要があります。すべてのオブジェクトが独自の役割を果たす必要があるため、ポインターがゼロになる可能性がある場合、ポインターはおそらく一種のフラグとして使用されます。


2
+1:私が経験したのと同じです。一部の一般的なデータストレージクラスだけがassignemtnおよびcopy-c'torを必要とします。さらに高レベルのビジネスオブジェクトフレームワークの場合、「一意のフィールドをコピーしない」や「別の親に追加する」などの処理も​​行うコピーの他の手法があるはずです。そのため、ビジネスオブジェクトのコピーには高レベルのフレームワークを使用します低レベルの割り当てを禁止します。
mmmmmmmm

2
+1:いくつかの合意事項:代入演算子の定義を妨げる参照メンバーは重要な議論ではありません。別の言い方をすると、すべてのオブジェクトが値のセマンティクスを持つわけではありません。また、論理的な理由なしに、コード全体に「if(p)」を散在させたくないことにも同意します。しかし、これに対する正しいアプローチは、クラスの不変式によるものです。明確に定義されたクラスは、メンバーがnullになり得るかどうかについて疑問の余地を残しません。コード内でポインタがnullになる可能性がある場合は、よくコメントされていると思います。
ジェームズホプキン

@JamesHopkinよく設計されたクラスはポインタを効果的に使用できることに同意します。参照は、NULLから保護するだけでなく、参照が初期化されたことも保証します(ポインターを初期化する必要がないため、ポインターは他のものを指すことができます)。参照は、所有権について通知します。つまり、オブジェクトはメンバーを所有していません。集計メンバーの参照を一貫して使用すると、ポインターメンバーが複合オブジェクトであることが非常に高くなります(ただし、複合メンバーがポインターで表される場合は多くありません)。
weberc2

11

7
私はそれを読みましたが、それはメンバーのデータではなく、パラメーターを渡すことについて話していると考えました。「参照は通常、オブジェクトのスキンに表示され、ポインタは内部に表示されます。」
markh44 2009年

はい、これはメンバーの参照のコンテキストでは良いアドバイスではないと思います。
Tim Angus 2017年

6

いくつかの重要なケースでは、割り当て可能性は単に必要ありません。これらは多くの場合、スコープを離れることなく計算を容易にする軽量のアルゴリズムラッパーです。このようなオブジェクトは、常に有効な参照を保持し、コピーする必要がないことを確認できるため、参照メンバーの主要な候補です

このような場合は、割り当て演算子(および多くの場合はコピーコンストラクター)を(継承boost::noncopyableまたはプライベート宣言することにより)使用不可にしてください。

ただし、ユーザーptsがすでにコメントしているため、他のほとんどのオブジェクトには同じことが当てはまりません。ここで、参照メンバーの使用は大きな問題になる可能性があるため、通常は回避する必要があります。


6

誰もが一般的なルールを配っているように見えるので、私は2つ提供します。

  • クラスメンバーとしてuseリファレンスを使用しないでください。私は自分のコードでこれを行ったことがなく(自分がこのルールに正しかったことを自分で証明した場合を除いて)、そうするケースを想像することはできません。セマンティクスは混乱しすぎており、リファレンスが設計されたものではありません。

  • 基本型を除いて、関数にパラメーターを渡すとき、またはアルゴリズムがコピーを必要とするときは、常に、常に参照を使用してください。

これらのルールは単純で、代わりに私を支えてくれました。他の人へのクラスメンバーとして、スマートポインター(ただしauto_ptrではない)の使用に関するルールを作成しておきます。


2
最初のものについては完全に同意しないでください。ただし、意見の相違は、理論的根拠を評価するための問題ではありません。+1
デビッドロドリゲス-ドリベス2009年

(しかし、私たちはSOの有権者とのこの議論に負けているようです)
James Hopkin

2
「決して、参照をクラスメンバーとして使用しない」という理由で、私は反対票を投じました。私の回答(および一番上の回答へのコメント)で説明したように、私自身の経験から、それが単に良いことである場合があります。私は彼らが良い方法でそれを使用している会社に雇われるまであなたのように思っていました、そしてそれが役立つ場合があることを私に明らかにしました。実際、私は本当の問題は、メンバーがプログラマーが何をしているかを理解することをメンバーに要求するときに参照を使用する構文と隠された含意に関するものだと思います。
クライム2009年

1
Neil>同意しますが、反対票を投じたのは「意見」ではなく、「まったくない」という主張です。ここでの議論は役に立たないようですので、私は反対票をキャンセルします。
クライム2009年

2
この回答は、参照メンバーのセマンティクスが混乱していることを説明することで改善できます。この理論的根拠は重要です。これは、一見すると、ポインターは意味的にあいまいに見えるためです(初期化されていない可能性があり、基礎となるオブジェクトの所有権について何も通知しません)。
weberc2

4

はい:参照を初期化したら変更してはならないので、参照を含むオブジェクトは割り当て可能ではないというのは本当ですか?

データメンバーの私の経験則:

  • 割り当てを妨げるため、参照を使用しないでください
  • クラスが削除を担当している場合は、ブーストのscoped_ptrを使用します(auto_ptrより安全です)。
  • それ以外の場合は、ポインタまたはconstポインタを使用します

2
ビジネスオブジェクトの割り当てが必要になるケースに名前を付けることもできません。
Mykola Golubyev、2009年

1
+1:auto_ptrメンバーに注意して追加します-それらが含まれるクラスには、割り当てとコピー構築が明示的に定義されている必要があります(生成されたものは必要とされる可能性が非常に低いです)
James Hopkin

auto_ptrも(賢明な)割り当てを防ぎます。

2
@Mykola:また、私のビジネスオブジェクトの98%には割り当てやコピー担当者が必要ないという経験もしました。私は、これらのクラスのヘッダーに追加する小さなマクロによってこれを防ぎます。これにより、実装なしでコピーc'torとoperator =()がプライベートになります。したがって、オブジェクトの98%では参照も使用しており、安全です。
mmmmmmmm

1
メンバーとしての参照は、クラスのインスタンスの寿命が他のクラス(または同じ)のインスタンスの寿命に直接依存していることを明示的に述べるために使用できます。「割り当てを防ぐため、参照を使用しないでください」は、すべてのクラスが割り当てを許可する必要があることを前提としています。それは、すべてのクラスがコピー操作を許可する必要があると仮定するようなものです...
Klaim 2009年

2

通常、メンバーデータでポインターを使用するのは、ポインターがnullになるか変更される可能性がある場合のみです。データメンバーの参照よりもポインターを優先する他の理由はありますか?

はい。コードの可読性。ポインターを使用すると、メンバーが参照(皮肉にも:))であり、包含されたオブジェクトではないことがより明確になります。昔ながらのことだと思う人もいますが、混乱や間違いを防ぐだけだと思います。


1
Lakosは、「大規模C ++ソフトウェアの設計」でもこれについて議論しています。
Johan Kotlinski、

Code Completeもこれを支持していると私は信じており、反対していません。しかし、それは過度に説得力があるわけではありません...
markh44

2

誰がクラスから派生するのか、彼らが何をしたいのかわからないので、参照データのメンバーにはお勧めしません。彼らは参照されたオブジェクトを利用したくないかもしれませんが、参照であるため、有効なオブジェクトを提供することを強制しました。参照データメンバーの使用を停止するのに十分な自分自身でこれを行いました。


0

質問を正しく理解したら...

ポインターの代わりに関数パラメーターとして参照: 指摘したように、ポインターは、ポインターのクリーンアップ/初期化の所有者を明確にしません。ポインターが必要な場合は共有ポイントを優先します。これはC ++ 11の一部です。また、ポイントされたデータを受け入れるクラスの存続期間を通じてデータの有効性が保証されない場合は、weak_ptrを使用します。; C ++ 11の一部でもあります。参照を関数パラメーターとして使用すると、参照がnullでないことが保証されます。この問題を回避するには、言語機能を破壊する必要があります。ルーズキャノンコーダーについては気にしません。

参照aaメンバー変数: データの有効性に関して上記のとおり。これは、ポイントされたデータが参照され、有効であることを保証します。

変数の有効性の責任をコードの前のポイントに移動すると、後のコード(例ではクラスA)がクリーンアップされるだけでなく、使用している人にもわかりやすくなります。

あなたの例では少し混乱しています(私は本当により明確な実装を見つけようとしています)、Bが使用するAはBの存続期間中保証されます。BはAのメンバーであるため、参照はこれを補強します(おそらく)より明確です。

また、念のため(コードのコンテキストでは意味をなさない可能性が低いため)、別の方法として、参照されていないポインターA以外のパラメーターを使用すると、Aがコピーされ、パラダイムが役に立たなくなります-Iこれが代替手段だとは本当に思っていません。

また、これにより、ポインターを変更できる参照データを変更できないことが保証されます。constポインターは、データへの参照/ポインターが変更可能でない場合にのみ機能します。

ポインターは、BのAパラメーターの設定が保証されていない場合、または再割り当てできる場合に役立ちます。また、弱ポインタ​​ーが実装に対して冗長すぎる場合もあり、多くの人々は、weak_ptrが何であるかを知らないか、単にそれらを嫌います。

この答えを引き裂いてください:)

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