原子力プログラマの備忘録

作る、遊ぶの爆発力

【C++】参照渡しについて語りたい

注意

この記事には、ある意味宗教的、派閥的な主張がありますが個人的見解ですので多めに見てください。

経緯

要望があったので、サークル内でゲーム開発向けのC/C++/C#の講座みたいなのを実施することになった。C言語久々だし何から教えたらいいか覚えてないな~(そんな状態で大丈夫か?)なので、いくつかのC言語講座サイトを巡り、必要事項をリストアップしていた。
そしていくつかのサイトで気になる内容を見かけた。


C言語の解説サイトなのに参照渡しの項目が存在するのである。


ちょっと何言ってるかわかんないですね...と思った私はその項目を覗いてみた。だいたいこういった内容をおっしゃっていた

関数の引数に、変数のポインタを渡すことを参照渡しといいます!

マジかと思った。そう、C言語にはそもそも参照渡しは存在しないのである。一緒にサイトを見てた後輩のF君*1もCに参照渡しって無いですよねって言ってた。なんだよわかってるじゃねえか。

C言語に参照渡しは無い?

先にも述べたがC言語には参照渡しという概念自体がない。ちなみに関数の引数にポインタを渡すのはポインタ渡しという。参照渡しとは言わない。

//※C言語での記述

//これはポインタ渡しという
void func(int *hoge){

/*渡されたポインタが指し示す先の変数に直接アクセスして10を足す*/
*hoge += 10;

}

そもそも、参照渡しというのは私の知る限りではC++にしかない概念である。以下がC++での参照渡しの記述。

//※C++での記述

//これこそまさに参照渡し
void func(int &hoge){

/*ポインタ渡しと同様、渡された元の変数を変更することができるが、
アクセスがより省略されている*/
hoge += 10;

}

やってることはほぼ一緒。特徴として引数に渡したもとの変数を直接いじくることができる。じゃあ一緒じゃん...となる気がするが、慌ててはいけない。ちなみにC++にはポインタ渡しも参照渡しも両方存在する。なんでこんな二通りの記述があるのか、もちろん必要だから追加されたのだ。

オブジェクト指向と参照渡し

細かな追加要素はあるものの、おおよそC++C言語オブジェクト指向がのっかったようなものだ。オブジェクト指向では主にクラス型から生成したインスタンスを利用するわけだが、インスタンスは生成と破棄のタイミングでコンストラクタ、デストラクタというメソッドが呼ばれる。これは関数の引数を生成したり破棄したりする際にも呼び出されてしまう。動的に確保したデータを保持し、自身のデストラクタで解放する機能を持ったオブジェクトのコピーを渡すと、渡した先でコピーが解放処理をしてしまうのでデータが保てなくなる恐れがある。
コピーを生成してはいけないのでポインタを渡せばいいのだが、ポインタを渡すのは渡す側も受け取る側も面倒くさい。そこで考案されたのが参照渡しだ。参照渡しでは受け取るのはデータのポインタだが、渡す側も利用する側もポインタを意識しない記述ができるというものだ。「めちゃくちゃ便利じゃん!じゃあもうポインタ渡しいらなくない?」となるのだが、参照渡しはそれはそれで不便な点があるのだ。

大切なのは利用する側への配慮

参照渡しが不便な点は以下のコードを見て頂けるとわかると思う。

/*引数+10の値を返す関数*/
int funcA(int _hoge){
 _hoge += 10;
 return _hoge;
}

/*引数にした変数に10を加算し、そのうえで引数と同じものを返す関数*/
int funcB(int &_hoge){
 _hoge += 10;
 return _hoge;
}

int main(void){
 //変数定義
 int a=0;
 int b=0;
 
//aにはb+10が代入される。bは変化なし。
 a = funcA(b);

//初期化
 a=0;
 b=0;

//funcAと記述は同じだが、aもbも10に変更される。
 a = funcB(b);

return 0;
}

つまり、関数を呼び出す側には渡した値が変更されるかどうかがわからないのである。一つでも参照渡しで引数のもとの変数の値を変更するような関数が紛れていると関数を利用する側は「あれ、この関数は渡した値が変更されるのか...?」とビクビクしながら利用するハメになる。VisualStudioとかならコメント等で説明が確認できるが自己説明的ではない。どのみちめちゃくちゃ非効率だ。
なので引数にした値を変更する場合はポインタ渡しにすることで呼び出す側が「こいつは渡したら変更されるんだ」という意識を持たせるということが重要だ。参照渡しにはconstを付けて明示的に変更しないことを証明すればなおいい。これで呼び出す側も安心だ。

/*引数にした変数に10を加算し、そのうえで引数と同じものを返す関数*/
/*ポインタを渡させることで変更を意識させる*/
int funcB(int *_hoge){
 _hoge += 10;
 return _hoge;
}

/*変更したくない場合はconstを付けて呼び出す側を安心させてあげよう*/
int funcC(const int &_hoge){
 _hoge += 10;
 return _hoge;
}

int main(void){
 //変数定義
 int a=0;
 int b=0;
 
//呼び出す側が&演算子を渡す変数につけることで変更されることを意識させる
 a = funcB(&b);

//初期化
 a=0;
 b=0;

//参照渡しだが、const修飾詞により変更されないので安心
 a = funcC(b);

return 0;
}

気になったこと

後輩ちゃんは「参照渡しは名前を変更する記述だと思ってました」みたいなことを言っていた。引数としてしか利用したことがないので眼中になかったが、なるほど。違う名前でもとの値が変更される変数が出来上がるので、名前変更用と考えられなくもない。

int main(void) {
    int a = 10;
    int &b = a;
    b++;

   //bにもaにも11が入っている。bは実質a?
}

私が未熟なのかわからないが、これを利用するパターンは思いつかない。スコープの関係からaだけが寿命が尽きる可能性があり、メモリ空間の確保していない場所を参照してしまう恐れがある。この使い方はしない方がいいのかな~...?

まとめ

今回はまあまあ真面目な内容だった。
言いたいことは2つ。

  • C言語に参照渡しは存在しない。ポインタを渡すのはポインタの値渡しという。人に教えるときは気を付けてね。
  • 参照渡しは値が変更されないけどコピーが作られると困るときに利用しよう。引数の値を変更する場合はポインタ渡しにして呼び出す側に変更を意識させよう。

以上!

*1:サークルの1回生。プログラミングの呑み込みが早くて多分そのうち実力抜かれる