web-dev-qa-db-ja.com

出力パラメーターと参照渡し

私は(私には)時代遅れのコーディングガイドラインを持つ新しいグループに参加しました。

しかし、有効なバックアップなしでマシンに反発するだけでは、どこにも行けません。
だから私はSOに目を向け、合理的な理由で/反対することができるかどうかを確認します(引数の両側が高く評価されるように、私のオプションが間違っているかもしれません)。

議論の余地があるガイドラインは次のとおりです。

ヒント:戻り引数の参照の代わりにポインターを使用します。

void Func1( CFoo &Return );  // bad  
void Func2( CFoo *pReturn ); // good  

正当化:
参照を使用すると、値と同じように見えます。呼び出し元は、関数を呼び出した後に値が変更されたことに驚くかもしれません。呼び出し先は、呼び出し元の値に影響を与えることを意味することなく、無害に値を変更できます。ポインターを使用することで、値を変更できることが呼び出し元と呼び出し先の両方に明確になります。参照の使用は、コードレビューで特に誤解を招く可能性があります。

39
Martin York

参照を使用すると、値と同じように見えます。

あなたが本当にあなたがしていることに注意を払っていない場合のみ。 OK、時にはそれは起こりますが、本当に...注意を払っていない人や自分が何をしているのか分からない人のためにコーディング標準の量を修正することはできません。

呼び出し元は、関数を呼び出した後に値が変更されたことに驚くかもしれません。

関数を呼び出したときに何が起こるかに驚いた場合、その関数の文書化は不十分です。

関数の名前、パラメーターリスト、およびおそらく非常に簡潔で説明的なドキュメントを考えると、関数が何をするのか、そして観察可能な副作用が何であるのか(引数が変更されているかどうかも含めて)明確にすべきです。

呼び出し先は、呼び出し元の値に影響を与えることを意味することなく、無害に値を変更できます。

関数がconst正しい場合、これは問題ではありません。関数がconst正しくない場合、可能な場合はconstを正しくする必要があります(コードをconstに正しく修正することは、絶対に失敗する可能性があります)。

ただし、この理論的根拠はあまり意味がありません。関数のコードを実際に書いているときは、パラメーターの宣言を見ることができるはずです。関数が長すぎてできない場合は、リファクタリングの時間です。

ポインターを使用することで、値を変更できることが呼び出し元と呼び出し先の両方に明確になります。

これは完全に正しいわけではありません。関数はconstオブジェクトへのポインターを取ることができます。その場合、オブジェクトは変更できません。

参照の使用は、コードレビューで特に誤解を招く可能性があります。

コードレビューを行っている人が自分が何をしているかわからない場合のみ。


これらはすべてうまく機能していますが、なぜポインター渡しではなく参照渡しを使用する必要があるのでしょうか?最も明白な理由は、参照をnullにすることはできませんです。

ポインターを受け取る関数では、少なくともデバッグアサーションを使用して、ポインターを使用する前に、ポインターがnullでないことを確認する必要があります。適切なコードレビュー中に、予期しない関数に誤ってNULLポインターを渡さないように、より多くのコードを分析する必要があります。この理由から、ポインター引数を取る関数を確認するのにはるかに長い時間がかかることがわかりました。ポインターを使用する場合、間違えるのは非常に簡単です。

34
James McNellis

constを適切に使用すると、(ほとんど)そのヒントの必要性がなくなると思われます。まだ便利だと思われる部分は、呼び出し元のコードを読むときです:

Func1(x);

xで何が行われているのかは明確ではありません(特に、Func1)。代わりに:

Func2(&x);

上記の規則で、xが変更されることを期待する必要があることを呼び出し元に示します。

18
Greg Hewgill

私はこのヒントのアドバイスを自分では使いませんが、正当化は有効です。そのため、C#のような言語では、呼び出しサイトで使用するoutおよびrefキーワードを導入しました。

私がそれに立ち向かうことができる最良の議論はこれです:人々にポインターを使用することを要求する代わりに、あなたは代わりにすべきです人々が関数が何をするかを反映する関数名を書くことを要求するstd::swapを呼び出すと、名前がそれを暗示しているため、引数の値が変更されることがわかります。一方、関数getSizeを呼び出す場合、引数を変更することは期待していません。

11
Peter Alexander

まだお持ちでない場合は、Herb SutterとAndrei Alexandrescuの「C++ Coding Standards:101 Rules、Guidelines and Best Practices」を購入してください。それを読んで。同僚に勧めてください。ローカルコーディングスタイルの優れた基盤です。

規則25では、著者は以下を推奨しています。

「引数が必要で、関数がそれへのポインターを保存しないか、所有権に影響しない場合、参照渡しをお勧めします。これは、引数が必要であり、呼び出し側が有効なオブジェクトを提供する責任があることを示します。」

「引数が必要」は、NULLが有効な値ではないことを意味します。

欠陥の最も頻繁な原因の1つは、nullポインタの偶発的な逆参照です。これらの場合にポインターの代わりに参照を使用すると、コンパイル時にこれらを削除できます。

そのため、トレードオフがあります-頻繁に発生するエラーの原因を排除するか、関数名以外の方法でコードを呼び出して理解できるようにします。個人的にはリスクを排除することに傾倒しています。

10
Andy Thomas

コーディング標準は、常識と同様に習慣に基づいています。あなたの同僚の中には、ポインタによって渡されないパラメータは変わらないという長年の根深い仮定に依存しているかもしれません。

コーディング標準の重要な部分は、それらが最適であるということではなく、コード本体にある程度の一貫性があるように誰もが遵守していることです。

7
Mark Ransom

呼び出しサイトでoutパラメーターの明示的な言及が本当に必要な場合は、ポインターが意味しないことを意味するようにハッキングする代わりに、実際にそれを要求する必要があります。ポインターは参照ほど変更を意味するものではありません。また、変更されていないオブジェクトにポインターを渡すことも珍しくありません。

パラメーターを明示的に表現する1つの潜在的な方法:

template<class T>
struct Out {
  explicit Out(T& obj) : base(obj) {}
  T& operator*() { return base; }
  T* operator->() { return &base; }
private:
  T& base;
};
template<class T>
Out<T> out(T& obj) {
  return Out<T>(obj);
}

void f(Out<int> n) {
  ++*n;
}

int main() {
  int n = 3;
  f(out(n));
  cout << n << '\n';
}

そして、古いコードをこれに変更するまでの一時的な手段として、Outをポインターや参照に変換可能にすることができます。

// in class definition
operator T*() { return &base; }
operator T&() { return base; }

// elsewhere
void old(int *p);
void g() {
  int n;
  old(out(n));
}

先に進み、これに必要なさまざまなクラスと、入出力パラメーターに、適切に劣化する方法で記述しました。私は その規則 をいつでもすぐに使用することを疑います(少なくともC++では)。

7
Roger Pate

これについては2つの学校があることがわかりました:

  • (a)ポインタを使用して、パラメータが変更される可能性があることを示す
  • (b)パラメータがnullの可能性がある場合にのみ、ポインタを使用します。

私はあなたの動機に同意します(a):コードを読んでいるとき、マウスオーバーで関数の宣言が与えられたとしても、すべての宣言を知ることはできません。数千行の何百もの関数をマウスで動かすには時間がかかります。

パラメーターを入出力する場合、ここで問題が発生します。

bool GetNext(int index, Type & result);

この機能の呼び出しは次のようになります。

int index = 3;
Type t; 
if (!GetNext(index, t)) 
  throw "Damn!";

その例では、tを潜在的に変更するために、呼び出し自体はかなり明白です。しかし、indexはどうですか?おそらくGetNextはインデックスをインクリメントするので、呼び出し先が呼び出し側の状態を維持する必要なく、常に次のアイテムを取得できますか?

通常は応答が発生します、メソッドはGetNextAndIncrementIndexであるか、とにかくイテレータを使用する必要があります。これらの人々は、Numerical Recipesがプログラミングの聖杯であるとまだ考えている電気技術者によって書かれたコードをデバッグする必要がなかったに違いない。

Howver私はまだ(b)の傾向があります:単に新しいコードを書くことで問題を回避できるため、「nullであるかどうか」が一般的な問題です。

2
peterchen

私はお勧め:

  • 参照渡し(ポインタで渡さない)
  • 可能な限りconst参照を渡します(コードベース全体でconstを正しく使用したと仮定)
  • リストのbeginningで変化する引数/パラメータを配置する
  • 関数に適切なラベルを付けます
  • 引数に適切なラベルを付けます(そして、詳細で説明的な名前といくつかの引数でメソッド/関数を作成します)
  • 結果を文書化する
  • 複数の引数/パラメーターが変化する場合、これらの引数を保持する単純なクラスを作成することを検討してください(参照自体であっても)
  • それでも視覚的および文書化されたキューがないと機能しない場合は、変化するパラメーターの軽量テンプレートコンテナーオブジェクトを作成し、それをメソッドまたは関数に渡します
1
justin

正当化は論理的に真実です。
値が変更されたことにコーダーを驚かせる可能性があります(値が値で渡されていると考えたため)。

しかし、論理的には、このコンテキストで意味を提供します。
したがって、値が変更される場合があります。これはコードの正確さにどのように影響しますか?
それとは別に、非論理的な人間が予期する値とは異なる値を出力する場合がありますが、コードは本来の動作を実行しており、コンパイラは制約を実施しています。

1
Martin York

私はこのガイドラインに同意しません。正当化で言及した混乱は、コードがconst-correctであることを確認することで簡単に解決できます。参照によって関数に入力パラメーターを渡す場合は、const参照にする必要があります。参照がconstでない場合、それは出力パラメーターであり、その値は関数によって変更される可能性があることを示しています。

さらに、参照ではなく関数にポインタを渡すと、これが動的に割り当てられたメモリへのポインタであるかどうか、および解放する必要があるかどうかに関する質問が即座に発生します。参照を使用すると、deleteを呼び出す誘惑がなくなります。

実際に動的に割り当てられたオブジェクトまたは配列へのポインターである場合や、nullであることが理にかなっている場合など、ポインターを渡すことが適切な場合があります。ただし、このような場合はスマートポインターをお勧めします。他のすべての場合、参照はより良いです、私見。

1
Dima