仮引数(パラメータ)と実引数(アーギュメント)
関数の**仮引数(パラメータ)**は定義に書かれた名前付きの変数で、**実引数(アーギュメント)**は呼び出すときに実際に渡す値です。前のページでは関数の定義と呼び出し方を説明しました。このページでは、それらの値が実際にどのように中へ渡されるかを扱います。C++にはいくつかの渡し方があり、その選択は正しさと速度の両方に影響するからです。
C++のデフォルトは値渡しで、関数はコピーを受け取ります。
addTen の中の n は、score から初期化された別個の変数です。n への再代入はそのコピーにしか影響しないので、main に戻ったときの score は変わりません。これは安全で予測しやすく、関数が誤ってデータを壊すことがありません。だからこそデフォルトになっているのです。
参照渡し:関数に呼び出し側を変更させる
ときには、関数に呼び出し側の変数を変更してほしいこともあります。パラメータの型に & を付けると、それは参照、つまりコピーではなく元の変数の別名になります。
最初の例との唯一の違いは & ですが、いまや n と score は同じオブジェクトです。これは複数の値を「返す」、あるいは何かをその場で更新するための標準的な方法です。典型的な使い方は2つの変数の入れ替えです。
& がなければ、swapValues は2つのコピーを入れ替えるだけで、main には何の変化も見えません - 初心者が非常によくやるバグです。
const参照:安価で読み取り専用のアクセス
値渡しは引数をコピーします。int ならコストはゼロですが、大きな string や vector を呼び出しのたびにコピーするのは、実際の無駄な作業です。その解決策が const 参照(const T&)です。参照の速さ(コピーなし)に加えて、引数を変更しないというコンパイラが保証する約束が得られます。
便利な目安として、小さな組み込み型(int、double、char、bool、ポインタ)は値渡しで、読み取るだけの大きなオブジェクトは const 参照で渡します。const でない素の T& は、呼び出し側のオブジェクトを本当に変更したい場合のために取っておきます。
微妙な落とし穴:素の int& n は一時オブジェクトやリテラルに束縛できません。最初の例の addTen(5) は、パラメータが int& だったらコンパイルできません。5 は別名を付けられる変数ではないからです。一方 const int& は 5 に束縛できます。これも const 参照が広く使われる理由の一つです。
デフォルト引数
パラメータに代わりの値を与えておけば、呼び出し側はそれを省略できます。引数がなければデフォルト値が使われます。
人がつまずく規則が2つあります。1つ目、デフォルト値は末尾でなければならない - あるパラメータがデフォルト値を持つと、それ以降のすべてのパラメータもデフォルト値を持たなければなりません。void f(int a = 1, int b) とは書けません。a を飛ばして b だけを渡す方法がないからです。2つ目、関数をヘッダで宣言し別の場所で定義する場合、デフォルト値は宣言にだけ書き、定義では決して繰り返してはいけません - 繰り返すとコンパイルエラーになります。
配列とベクタを渡す
生の配列は渡されるときにポインタへと成り下がるため、関数はそのサイズを見失います - ほとんどの場合、長さを一緒に渡します。
配列がポインタになったため、sum の中での sizeof(arr) は配列ではなくポインタのサイズを返します - 有名なバグです。モダンC++では、自分自身のサイズを保持する std::vector(C++20では std::span)を const 参照で渡すことを推奨します。
const& に注目してください。これを外すと呼び出しのたびにベクタ全体がコピーされます。要素が4つのベクタなら無害ですが、100万要素なら見えない性能の落とし穴になります。
ポインタパラメータ
ポインタ(T*)を渡すこともできます。参照と同じく、これによって関数は呼び出し側のデータに手を伸ばせますが、ポインタは付け替えられたりヌルになったりします - つまり「値なし」が正当な選択肢である場合に適したツールです。
呼び出し側はアドレスを共有するために &value を渡し、関数は *out を通じて書き込みます。参照との重要な違いは、ポインタは nullptr になりうることです。そのため、ポインタを受け取る関数はデリファレンスする前にチェックすべきです - このガードを飛ばしてヌルポインタをデリファレンスするのは未定義動作で、たいていはクラッシュします。「値なし」がまったく意味を成さないなら、そもそもヌルになりえない参照のほうがすっきりします。
次へ:参照
パラメータは参照がその真価を発揮する場所ですが、参照はそれ自体が一つの機能です - 関数のシグネチャの中だけでなく、任意の変数に対して作れる別名なのです。次のページでは参照そのものの仕組みを掘り下げます。宣言の仕方、なぜ即座に初期化しなければならないのか、lvalue参照と const 参照の違い、そして参照がどのようにして宙ぶらりん(dangling)になりうるかという微妙な点を見ていきます。
よくある質問
C++における値渡しと参照渡しの違いは何ですか?
値渡しは引数をパラメータにコピーするため、関数内での変更は呼び出し側に影響しません。参照渡し(int&)はパラメータを呼び出し側の変数の別名にするため、変更が外部からも見えます。コピーするには void f(int x) を、元の変数を変更するには void f(int& x) を使います。
C++ではいつconst参照パラメータを使うべきですか?
大きなオブジェクトを、コピーせず、かつ関数に変更させずに読み取りたいときに const T& を使います - 例えば void print(const string& s) です。参照渡しの速さと、値渡しの安全性の両方が得られます。int や char のような小さな型では、単純な値渡しでも同じくらい高速です。
C++のデフォルト引数とは何ですか?
デフォルト引数は、呼び出し側がパラメータを省略したときに代わりの値を取らせる仕組みです。例えば void greet(string name = "there") のように書きます。デフォルト値は末尾(右側)のパラメータでなければならず、宣言と定義が分かれている場合は宣言にのみ指定し、定義には書きません。