C++の関数呼び出しにおいて、引数を渡す基本的な方法は「値渡し(Pass-by-Value)」です。しかし、値渡しには「呼び出し元の変数を変更できない」という限界があります。
この問題を解決し、関数が呼び出し元のデータを直接操作できるようにする仕組みが「参照渡し(Pass-by-Reference)」です。
値渡しの限界
前回の記事で解説したように、「値渡し」では、関数の仮引数には実引数のコピーが渡されます。そのため、関数内で仮引数の値をいくら変更しても、呼び出し元のオリジナル変数には一切影響がありません。
例えば、「ユーザーのスコアを0にリセットする」関数を値渡しで作成すると、意図した通りに動作しません。
#include <iostream>
// (失敗例) 値渡しでスコアをリセットしようとする
void resetScore_PassByValue(int userScore) {
// ここで変更しているのは「コピー」された userScore
userScore = 0;
std::cout << " (関数内) スコアを " << userScore << " にリセットしました。\n";
}
int main() {
int myScore = 1500;
std::cout << "(呼び出し前) 現在のスコア: " << myScore << "\n";
// myScore (1500) の「コピー」が関数に渡される
resetScore_PassByValue(myScore);
std::cout << "(呼び出し後) 現在のスコア: " << myScore << "\n";
return 0;
}
実行結果:
(呼び出し前) 現在のスコア: 1500
(関数内) スコアを 0 にリセットしました。
(呼び出し後) 現在のスコア: 1500
main 関数内の myScore は 1500 のまま変更されていません。
参照 (Reference) とは?
この問題を解決するのが「参照」です。
**参照(Reference)は、C++の機能で、すでに存在する変数(オブジェクト)に別名(エイリアス)**を付けるものです。参照は & 記号を使って宣言します。
#include <iostream>
int main() {
int originalValue = 10;
// originalValue への参照(別名)として refValue を宣言
int& refValue = originalValue;
std::cout << "オリジナル: " << originalValue << "\n"; // 10
std::cout << "参照: " << refValue << "\n"; // 10
// 参照(別名)の値を変更する
refValue = 100;
std::cout << "--- 変更後 ---\n";
std::cout << "オリジナル: " << originalValue << "\n"; // 100 (オリジナルが変わる)
std::cout << "参照: " << refValue << "\n"; // 100
return 0;
}
refValue は originalValue のコピーではなく、originalValue そのものを指す別の名前になります。そのため、refValue への操作は、originalValue への操作と全く同じになります。
参照渡し (Pass-by-Reference)
この「参照」の仕組みを関数の仮引数に応用したものが「参照渡し」です。
仮引数の型を int から int& に変更するだけで、その仮引数は「渡された変数のコピー」ではなく、「渡された変数そのものの別名(参照)」として機能します。
参照渡しによる swap 関数の修正
先ほどの resetScore 関数を、参照渡しを使って正しく動作するように修正します。
#include <iostream>
// (成功例) 参照渡しでスコアをリセットする
// 仮引数 userScore は、渡された変数の「参照」となる
void resetScore_PassByReference(int& userScore) {
// ここでの変更は、呼び出し元の変数「そのもの」を変更する
userScore = 0;
std::cout << " (関数内) スコアを " << userScore << " にリセットしました。\n";
}
int main() {
int myScore = 1500;
std::cout << "(呼び出し前) 現在のスコア: " << myScore << "\n";
// myScore (1500) の「参照」が関数に渡される
resetScore_PassByReference(myScore);
std::cout << "(呼び出し後) 現在のスコア: " << myScore << "\n";
return 0;
}
実行結果:
(呼び出し前) 現在のスコア: 1500
(関数内) スコアを 0 にリセットしました。
(呼び出し後) 現在のスコア: 0
今度は main 関数内の myScore が正しく 0 に変更されました。
参照渡しの応用と注意点
参照渡しは、関数が複数の値を変更する必要がある場合に特に有効です。
応用例:2つの口座残高を調整する
以下の例では、transferFunds(資金移動)関数が、参照渡しを使って2つの異なる変数(accountA と accountB)の値を同時に操作しています。
#include <iostream>
// 口座Aから口座Bへ指定額を移動する
void transferFunds(int& fromAccount, int& toAccount, int amount) {
// 参照を介して呼び出し元の変数を直接変更する
fromAccount -= amount;
toAccount += amount;
}
int main() {
int accountA = 50000;
int accountB = 10000;
std::cout << "--- 移動前 ---\n";
std::cout << "口座A: " << accountA << "円\n";
std::cout << "口座B: " << accountB << "円\n";
// 15000円を A から B へ移動
transferFunds(accountA, accountB, 15000);
std::cout << "--- 移動後 ---\n";
std::cout << "口座A: " << accountA << "円\n"; // 35000
std::cout << "口座B: " << accountB << "円\n"; // 25000
return 0;
}
注意点:呼び出し側の見た目
参照渡しには重要な注意点があります。それは、関数を呼び出す側のコードの見た目が、値渡しと全く同じであることです。
int value = 100;
resetScore_PassByValue(value); // 値渡し
resetScore_PassByReference(value); // 参照渡し (見た目は同じ!)
呼び出し側(main関数など)のコードだけを見ても、その関数が value を変更する可能性がある(参照渡し)のか、絶対に変更しない(値渡し)のか、区別がつきません。
このため、関数を呼び出す際は、その関数の**宣言(シグネチャ)**を常に確認し、仮引数が int& のように & 付きになっていないか注意する必要があります。
まとめ:const 参照という現代的なテクニック
「参照渡し」は、呼び出し元の変数を変更できる強力な機能ですが、意図しない変更を引き起こす危険性も伴います。
- 値を変更する必要がある場合:
void func(int& x)- 呼び出し元の値を変更したい場合にのみ、参照渡しを使用します。
- 値を変更しないが、コピーを避けたい場合:
void func(const std::string& s)std::stringやstd::vectorのような大きなオブジェクトを「値渡し」すると、コピーに時間がかかります。- このような場合、
const(変更不可)を付けた「const参照渡し」を使います。 - これにより、コピーのコストを回避しつつ(高速)、関数内で呼び出し元の変数を誤って変更することも防ぐ(安全)という、両方のメリットを得ることができます。
現代のC++プログラミングでは、この const 参照渡しが非常に重要なテクニックとなっています。
