何かを参照する 2 つの方法
すでにポインタはご存じでしょう。アドレスを格納し、そこに存在するオブジェクトへ到達できる変数です。参照は、既存のオブジェクトを間接的に扱うために C++ が用意するもう一つの道具です。両者は十分に重なり合うため、初心者はどちらを選ぶべきか分からないことが多く、このページでは両者を並べて比べます。
手短に言えば、**参照は別名(エイリアス)**です。int& r = x; が実行されると、r は x そのものになります。同じオブジェクトで、名前が違うだけです。ポインタは独立したオブジェクトで、たまたま別のオブジェクトのアドレスを保持しています。この一点の違いが、ほかのすべてを決定づけます。
参照は別名である
参照は生成された瞬間にオブジェクトへ結び付けられなければならず、それ以降は参照を使うたびに元のオブジェクトに触れることになります。
使用箇所で参照外しのための * も、「アドレスを取る」ための & もないことに注目してください。alias を普通の int とまったく同じように読み書きします。int& alias の & は型の一部であって、アドレス取得演算子ではありません。
どこが違うのか
以下の振る舞いこそ、両方の道具が存在する理由そのものです。これは暗記すべき表です。
// reference pointer
// must be initialized? yes no (but should be)
// can be null? no yes (nullptr)
// can be reseated? no yes
// pointer arithmetic? no yes
// syntax to use it just the name *p or p->member
// taking address &ref == &original &p is the pointer's own address
このうち 2 つが特に人を混乱させます。まず、参照は決して再束縛できません。参照への代入は、参照先のオブジェクトの中へ値をコピーするだけで、参照を新しいものへ向け直すわけではありません。
一方、ポインタはいつでも自由に別の場所を指せます。
次に、参照は決して正当に null になれませんが、ポインタはなれます。これにより「値なし」をポインタでは表現できますが参照では表現できません。これはあなたが常に頼ることになる性質です。
関数引数での選択
選択が最も顕著に表れるのがここです。関数が呼び出し側のオブジェクトを読み書きする必要があるとき、どちらでも機能しますが、伝える意図が異なります。
参照版(addTax(cart))は「何もない」状態で呼び出すことが不可能なので、関数の中で null をチェックすることは決してありません。オブジェクトは必ずそこにあると保証されています。ポインタ版(applyDiscount(&cart))は & を通じて、引数が変更されるかもしれないことを呼び出し箇所で示し、呼び出し側が「該当なし」を意味する nullptr を渡せるようにします。あなたの関数に合う保証を持つほうを選んでください。
大きな型の読み取り専用引数では、慣用的な選択は const T& です。コピーを避け、変更しないことを約束します。値渡しと参照渡しについての詳細は関数引数を参照してください。
シンプルな経験則
迷ったら、まずは参照を既定とし、参照に欠けている機能が必要なときだけポインタへ格上げしましょう。
- オブジェクトが常に存在し、その同一性が決して変わらないときは参照を使う。関数引数や別名に共通する一般的なケースです。
- 次のいずれかが当てはまるときはポインタを使う:
- 「何もない」が有効な状態である(省略可能な引数、一致が見つからないかもしれない検索)。ポインタは
nullptrになれます。 - 時間とともに異なるオブジェクトを指す必要がある。ポインタは付け替えられます。
deleteで解放するヒープメモリを管理している、またはポインタ算術で配列をたどっている。
- 「何もない」が有効な状態である(省略可能な引数、一致が見つからないかもしれない検索)。ポインタは
これらのいずれにも当てはまらないなら、参照のほうがすっきりして安全な選択です。コンパイラが「常に有効、決して付け替えられない」をあなたの代わりに保証してくれるからです。
避けるべきよくある間違い
ref = otherで再束縛されると思い込む。 それは参照先のオブジェクトの中へ値を代入します。参照は一生束縛されたままです。付け替えが必要ならポインタを使ってください。- ローカル変数への参照(またはポインタ)を返す。
int& f() { int x = 5; return x; }はダングリング参照を返します。fが戻るとxは消滅し、その結果を使うのは未定義動作です。同じ罠はポインタにも当てはまります(return &x;)。 - 「null 参照」を偽造する。
pがnullptrのときにint& r = *p;と書くのは、安全な「空の」参照ではなく、参照外しした瞬間に未定義動作です。省略可能性はポインタかstd::optionalで表現してください。 - 習慣でポインタに手を伸ばす。 引数が常に存在し、決して付け替えないなら、参照は丸ごと一種類分の null チェックとクラッシュを取り除きます。使わない機能の代償を払ってはいけません。
次へ:動的メモリ
ここまで、あなたが参照したり指したりしたオブジェクトはすべてスタック上に自動的に作られていました。次のページ、動的メモリでは new と delete を扱います。実行時にオペレーティングシステムへメモリを要求すること、なぜ(参照ではなく)ポインタがそれを所有するのか、そして解放を忘れるとどのようにリークが起きるのかを説明します。
よくある質問
C++ における参照とポインタの違いは何ですか?
参照は既存オブジェクトの別名です。必ず初期化が必要で、決して null になれず、後から別のオブジェクトを指すように変えることもできません。ポインタはアドレスを保持する独立した変数です。null になれ、別の場所を指すよう付け替えられ、ポインタ算術もサポートします。参照には & の構文を、ポインタには */-> を使います。
C++ で参照ではなくポインタを使うべきなのはいつですか?
「何もない」が有効な状態であるとき(省略可能な引数、見つからなかった結果)、時間とともに異なるオブジェクトを指すよう付け替える必要があるとき、または delete で解放するヒープメモリを所有するときはポインタを使います。オブジェクトが常に存在し、その同一性が決して変わらないときは参照を使います。これはほとんどの関数引数に当てはまります。
C++ で参照は null になれますか?
いいえ。有効な参照は常に実在するオブジェクトを指すため、null かどうかをチェックすることはありません。null ポインタを参照外しして参照を作ると(p が null のときの int& r = *p;)、null 参照ではなく未定義動作になります。「もしかすると何もない」を表現したいときは、ポインタか std::optional を使ってください。