C ++で参照渡しよりもポインタ渡しの利点はありますか?


225

C ++で参照渡しよりポインタ渡しの利点は何ですか?

最近、参照で渡すのではなく、ポインタで関数の引数を渡すことを選択した多くの例を見てきました。これを行うメリットはありますか?

例:

func(SPRITE *x);

の呼び出しで

func(&mySprite);

func(SPRITE &x);

の呼び出しで

func(mySprite);

newポインタとその結果生じる所有権の問題を作成することを忘れないでください。
マーティンヨーク

回答:


216

ポインターはNULLパラメーターを受け取ることができますが、参照パラメーターはできません。「オブジェクトなし」を渡したい可能性がある場合は、参照ではなくポインタを使用してください。

また、ポインタによる受け渡しにより、呼び出しサイトでオブジェクトが値によって受け渡されるのか、参照によって受け渡されるのかを明示的に確認できます。

// Is mySprite passed by value or by reference?  You can't tell 
// without looking at the definition of func()
func(mySprite);

// func2 passes "by pointer" - no need to look up function definition
func2(&mySprite);

18
不完全な答え。ポインターを使用しても、一時オブジェクトまたは昇格されたオブジェクトの使用は許可されません。また、スタックのようなオブジェクトとしてのポイントされたオブジェクトの使用も許可されません。また、ほとんどの場合、NULL値を禁止する必要があるときに、引数がNULLになる可能性があることを示唆しています。完全な答えについては、litbの答えを読んでください。
paercebal 2008

2番目の関数呼び出しは、注釈が付けられていましたfunc2 passes by reference。コードレベルの観点でポインターを渡すことによって実装された、高レベルの観点から「参照によって」渡すことを意味していたと思いますが、これは非常に混乱しました(stackoverflow.com/questions/13382356/…を参照)。
オービットの

これは買わない。はい、あなたはポインターを渡します、それでそれが出力パラメーターでなければならないので、何が指しているのがconstではあり得ないのですか?
deworde

Cでこの渡し参照はありませんか?私はcodeblock(mingw)の最新バージョンを使用しており、その中でCプロジェクトを選択しています。参照渡し(func(int&a))は引き続き機能します。それともC99またはC11以降で利用できますか?
Jon Wheelock、2015年

1
@JonWheelock:いいえ、Cには参照渡しはありません。 func(int& a)標準のどのバージョンでも有効なCではありません。誤ってファイルをC ++としてコンパイルしている可能性があります。
Adam Rosenfield、2015年

245

ポインタを渡す

  • 発信者はアドレスを取得する必要があります->透過的ではありません
  • 0の値を指定して、を意味することができますnothing。これは、オプションの引数を提供するために使用できます。

参照渡し

  • 呼び出し元はオブジェクトを渡すだけです->透明です。ポインタ型のオーバーロードは不可能なので、演算子のオーバーロードに使用する必要があります(ポインタは組み込み型です)。したがってstring s = &str1 + &str2;、ポインタを使用して行うことはできません 。
  • 可能な0値はありません->呼び出された関数はそれらをチェックする必要がありません
  • constへの参照は一時変数も受け入れます。一時変数void f(const T& t); ... f(T(a, b, c));のアドレスを取得できないため、ポインターをそのように使用することはできません。
  • 最後に重要なことですが、参照は使いやすい->バグの可能性が低くなります。

7
ポインタを渡すと、「所有権が譲渡されたかどうか」が発生します。質問。これはリファレンスには当てはまりません。
Frerich Raabe、

43
私は「バグの可能性が少ない」には同意しません。呼び出しサイトを検査すると、読者が「foo(&s)」を見ると、sが変更される可能性があることがすぐにわかります。"foo(s)"を読んでも、sが変更されるかどうかはまったくわかりません。これはバグの主な原因です。おそらく、特定の種類のバグが発生する可能性は低いですが、全体として、参照渡しはバグの大きな原因です。
William Pursell

25
「透明」とはどういう意味ですか?
Gbert90

2
@ Gbert90、呼び出しサイトでfoo(&a)を見つけた場合、foo()がポインター型を取ることがわかります。foo(a)が表示されている場合、それが参照を取得するかどうかはわかりません。
マイケルJ.ダベンポート

3
@ MichaelJ.Davenport-説明では、「透明」は、「呼び出し元がポインタを渡していることは明らかだが、呼び出し元が参照を渡していることは明らかではない」という言葉に沿ったものを意味すると提案します。ヨハネスの投稿で、彼は「ポインターによる受け渡し-呼び出し元はアドレスを取得する必要がある->透過的ではない」および「参照渡し-呼び出し元はオブジェクトを渡すだけ->透過的」と言っています。 。私は、Gbert90の質問「「透明」とはどういう意味ですか?
ハッピーグリーンキッドナップズ

66

「cplusplus.com」の記事による推論が好きです。

  1. 関数がパラメーターを変更する必要がなく、値をコピーするのが簡単な場合(int、double、char、boolなど...単純型。std:: string、std :: vector、およびその他のすべてのSTLの場合、値渡しします。コンテナーは単純なタイプではありません。)

  2. 値のコピーに負荷がかかり、かつ関数がAND NULLを指す値を変更したくない場合は、constポインターを渡します。これは、関数が処理する有効な期待値です。

  3. 値のコピーにコストがかかり、関数がAND NULLが指す値を変更する必要がある場合、非constポインターを渡します。これは、関数が処理する有効な期待値です。

  4. 値をコピーするのに負荷がかかり、関数が参照された値を変更したくない場合にconst参照で渡すAND AND NULLは、代わりにポインターが使用された場合、有効な値ではありません。

  5. 値のコピーにコストがかかり、かつ関数が参照する値を変更する必要がある場合に非cont参照で渡し、AND NULLが参照されている値を変更したい場合、代わりにポインターが使用された場合は有効な値ではありません。

  6. テンプレート関数を記述する場合、明確な答えはありません。考慮すべきトレードオフがいくつかあるため、この説明の範囲を超えていますが、ほとんどのテンプレート関数は値または(const)参照によってパラメーターを取ると言えば十分ですただし、イテレータの構文はポインタの構文と似ているため(アスタリスクは「逆参照」)、イテレータを引数として期待するテンプレート関数は、デフォルトでポインタも受け入れます(NULLイテレータの概念は構文が異なるため、NULLをチェックしません) )。

http://www.cplusplus.com/articles/z6vU7k9E/

これから私が理解することは、ポインターまたは参照パラメーターを使用することを選択することの間の主な違いは、NULLが許容可能な値であるかどうかです。それでおしまい。

結局のところ、値が入力、出力、変更可能などであるかどうかは、関数に関するドキュメント/コメントに記載する必要があります。


はい、私にとってNULL関連の用語がここでの主な関心事です。引用用のThx ..
binaryguy

62

アレン・ホルブの「足で自分を撃つのに十分なロープ」には、次の2つのルールがリストされています。

120. Reference arguments should always be `const`
121. Never use references as outputs, use pointers

参照がC ++に追加された理由をいくつか挙げています。

  • コピーコンストラクタを定義するために必要です
  • オペレーターのオーバーロードに必要です
  • const 参照により、コピーを回避しながら、値渡しのセマンティクスを持つことができます

彼の主なポイントは、呼び出しサイトではパラメーターが参照パラメーターであるか値パラメーターであるかを示すものがないため、参照を「出力」パラメーターとして使用しないことです。したがって、彼のルールはconst、引数として参照のみを使用することです。

個人的には、パラメーターが出力パラメーターであるかどうかがより明確になるので、これは良い経験則であると思います。しかし、私は個人的にはこれに一般的に同意しますが、出力パラメーターを参照として主張する場合、自分のチームの他のメンバーの意見に動揺することを許可します(非常に好きな開発者もいます)。


8
その引数の私のスタンスは、関数名がドキュメントをチェックせずにparamが変更されることを完全に明白にする場合、非const参照はOKであるということです。だから個人的には「getDetails(DetailStruct&result)」を許可します。そこでのポインタは、NULL入力の醜い可能性を引き起こします。
スティーブジェソップ

3
これは誤解を招くものです。参照を好まない人もいますが、それらは言語の重要な部分であり、そのように使用する必要があります。この推論の行は、任意の型を格納するために常にvoid *のコンテナーを使用できるテンプレートを使用しないと言っているようなものです。litbによる回答を読んでください。
デビッドロドリゲス-ドリベス2008

4
これがどのように誤解を招くのかはわかりません。参照が必要な場合もあれば、ベストプラクティスが可能であれば使用しないことを提案する場合もあります。言語のすべての機能についても同じことが言えます-継承、非メンバーの友達、オペレーターのオーバーロード、MIなど...
マイケルバー

ちなみに、litbの答えは非常に優れており、確かにこれよりも包括的であることに同意します。参照を出力パラメーターとして使用しないようにする根拠の説明に焦点を当てることを選択しました。
マイケル・バー、

1
:このルールは、GoogleのC ++スタイルガイドで使用されているgoogle-styleguide.googlecode.com/svn/trunk/...
アントンDaneyko

9

上記の投稿の説明:


参照は、null以外のポインタを取得することを保証するものではありません。(私たちはしばしばそれらをそのように扱いますが。)

恐ろしく悪いコードですが、ウッドシェッドの悪いコードの背後にいるように、以下はコンパイルして実行します(少なくとも私のコンパイラの下で)。

bool test( int & a)
{
  return (&a) == (int *) NULL;
}

int
main()
{
  int * i = (int *)NULL;
  cout << ( test(*i) ) << endl;
};

私が参照に関して持っている本当の問題は、コンストラクターで割り当て、デストラクターで割り当て解除し、コピーコンストラクターまたはoperator =()を提供できない他のプログラマー(以降、IDIOTSと呼ばれる)にあります。

突然、foo(BAR bar)foo(BAR bar)の間には違いの世界があります。(ビット単位の自動コピー操作が呼び出されます。デストラクタの割り当て解除が2回呼び出されます。)

ありがたいことに、最近のコンパイラーは、同じポインターのこの二重割り当て解除を受け取ります。15年前はそうではありませんでした。(gcc / g ++では、setenv MALLOC_CHECK_0を使用して古い方法に戻ります。)結果として、DEC UNIXでは、同じメモリが2つの異なるオブジェクトに割り当てられます。そこにはたくさんのデバッグの楽しみがあります...


より実際的には:

  • 参照は、他の場所に保存されているデータを変更していることを隠します。
  • 参照とコピーしたオブジェクトを混同するのは簡単です。
  • ポインターはそれを明らかにします!

16
それは関数や参照の問題ではありません。あなたは言語のルールを破っています。nullポインターを単独で逆参照することは、すでに未定義の動作です。「参照はnull以外のポインタを取得することを保証するものではありません。」:標準自体はそうだとしています。他の方法は未定義の動作を構成します。
Johannes Schaub-litb 2008

1
私はlitbに同意します。あなたが私たちに見せているコードは真実ですが、他のものよりも妨害行為です。「参照」表記と「ポインタ」表記の両方を含め、何かを妨害する方法があります。
paercebal

1
私はそれを「森の小屋の悪いコードの後ろに連れ出す」だと言っていました!同じように、i = new FOOも使用できます。私を削除します。test(* i); 別の(残念ながら一般的な)ダングリングポインター/参照の発生。
Mr.Ree

1
それは実際にはいない逆参照の問題だNULLをではなく、使用したその間接参照(ヌル)オブジェクトを。そのため、言語実装の観点からは、ポインタと参照の間に(構文以外に)違いはありません。期待が異なるのはユーザーです。
Mr.Ree

2
返された参照をどのように処理するかに関係なく、言う瞬間*i、プログラムは未定義の動作をします。たとえば、コンパイラはこのコードを見て、「OK、このコードはすべてのコードパスで未定義の動作をしているため、この関数全体が到達不能でなければならない」と想定できます。次に、この関数につながるすべての分岐が行われないと仮定します。これは定期的に実行される最適化です。
David Stone

5

ここでの回答のほとんどは、意図を表現するという点で、関数シグネチャに生のポインタを持っていることの固有のあいまいさには対処できません。問題は次のとおりです。

  • 呼び出し元は、ポインタが単一のオブジェクトを指しているのか、それともオブジェクトの「配列」の先頭を指しているのかわかりません。

  • 呼び出し元は、ポインタが指すメモリを「所有」しているかどうかはわかりません。IE、関数がメモリを解放する必要があるかどうか。(foo(new int)-これはメモリリークですか?)。

  • 呼び出し元はnullptr、関数に安全に渡すことができるかどうかを知りません。

これらの問題はすべて参照によって解決されます。

  • 参照は常に単一のオブジェクトを参照します。

  • 参照は、参照するメモリを所有することはなく、単にメモリへのビューです。

  • 参照をnullにすることはできません。

これにより、参照は一般的な使用に適した候補になります。ただし、参照は完全ではありません。考慮すべき主要な問題がいくつかあります。

  • 明示的な間接指定はありません。これを使用する必要があるので、これは生のポインタの問題ではありません&、ポインタを渡していることを示す演算子を。たとえばint a = 5; foo(a);、aが参照渡しされており、変更できることは、ここではまったく明確ではありません。
  • ヌラビリティ。このポインタの弱点は、参照をnullにできるようにしたい場合の強みにもなります。よう見std::optional<T&>(正当な理由のために)有効ではありません、ポインタは私たちにあなたがしたいことNULL値の許容を与えます。

それで、明示的な間接指定を伴うnull可能な参照が必要な場合は、T*権利に到達する必要があるようです。違う!

抽象化

ヌル可能性を求める必死の中で、私たちはに手を伸ばしT*、前述の欠点と意味のあいまいさをすべて無視することができます。代わりに、C ++が最も得意とすること、つまり抽象化に到達する必要があります。ポインターをラップするクラスを記述するだけで、表現可能性が得られるだけでなく、null可能性と明示的な間接参照も得られます。

template <typename T>
struct optional_ref {
  optional_ref() : ptr(nullptr) {}
  optional_ref(T* t) : ptr(t) {}
  optional_ref(std::nullptr_t) : ptr(nullptr) {}

  T& get() const {
    return *ptr;
  }

  explicit operator bool() const {
    return bool(ptr);
  }

private:
  T* ptr;
};

これは私が思いつくことができる最もシンプルなインターフェースですが、それは効果的に機能します。参照の初期化、値が存在するかどうかの確認、および値へのアクセスを可能にします。次のように使用できます。

void foo(optional_ref<int> x) {
  if (x) {
    auto y = x.get();
    // use y here
  }
}

int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability

私たちは目標を達成しました!生のポインタと比較して、利点を見てみましょう。

  • インターフェイスは、参照が1つのオブジェクトのみを参照する必要があることを明確に示しています。
  • ユーザー定義のデストラクタがなく、メモリを削除する方法がないため、参照するメモリを所有していないことは明らかです。
  • nullptr関数の作成者が明示的に要求しているので、呼び出し元は渡されることができますoptional_ref

ここから、等価演算子、モナディックget_orおよびmapインターフェース、値を取得するメソッド、または例外をスローするメソッドを追加するなど、インターフェースをより複雑にすることができますconstexpr。それはあなたがすることができます。

結論として、生のポインタを使用する代わりに、それらのポインタが実際にコードで何を意味するのかを推論し、標準ライブラリの抽象化を利用するか、独自に作成します。これにより、コードが大幅に改善されます。


3

あんまり。内部的には、参照による受け渡しは、参照されるオブジェクトのアドレスを本質的に渡すことによって実行されます。したがって、ポインタを渡すことによる効率の向上はありません。

ただし、参照渡しは1つの利点があります。渡されるオブジェクト/タイプのインスタンスがあることが保証されています。ポインタを渡すと、NULLポインタを受け取る危険があります。参照渡しを使用すると、関数の呼び出し元に暗黙のNULLチェックを1レベル押し上げます。


1
それは長所と短所の両方です。多くのAPIは、NULLポインタを使用して何か有用なものを意味します(つまり、NULLタイムスペックは永久に待機しますが、値はそれだけ長く待機することを意味します)。
グレッグロジャース

1
@Brian:私は、NIT-狩りになりたいが、ありません:私は考えていないものがあると言う保証の参照を取得するときに、インスタンスを取得します。関数の呼び出し元が呼び出し先が知ることができないぶら下がりポインタを逆参照する場合でも、ぶら下がり参照は可能です。
08

参照を使用することでパフォーマンスを得ることができる場合もあります。参照を使用する必要がなく、アドレスを割り当てられていないためです。間接指定は必要ありません。
Johannes Schaub-litb 2008

ダングリングリファレンスを含むプログラムは、有効なC ++ではありません。したがって、はい、コードすべての参照が有効であると想定できます。
Konrad Rudolph、

2
私は間違いなくnullポインターを逆参照することができ、コンパイラーはそれを知ることができません...コンパイラーがそれが「無効なC ++」であることを伝えられない場合、それは本当に無効ですか?
rmeador 2008
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.