C++ではポインタ渡しよりも参照渡しを使いたい。
既に多くの人が指摘しているでしょうけれども、参照渡しをすることで安全なコーディングをすることができます。
ポインタを使うと発生する心配の種
・宣言だけで、領域確保がされていない。
・領域は確保されているが初期化されていない。
・ポインタの値がインクリメントされて、別のアドレスを指し示すようになっていることを見逃す可能性
・領域が解放されている可能性
・領域が解放されているのにもかかわらす、ポインタの変数がNULLになっていない可能性。
・確保済みの領域に対して、指し示すように与えられたポインタ変数がある場合、誰が責任を持って確保済みの領域を解放するのか?
ポインタを関数・メソッドの引数に使うと発生する心配の種
・呼び出し側と呼ばれる側で変数の表記が異なる。
・呼び出し側と呼ばれる側で、ポインタの値がNULLでないことを確認する必要を生じる。
・引数に与えられた変数の領域の確保に責任を持っているのは誰?
(呼び出し側、呼ばれた側?)
参照渡しを使うと回避される心配の種
・呼び出し側と呼ばれる側で、変数の表記が共通にできるので、記述や読解が楽になる。
point.x, point.y
という表記と
point->x, point->y
という表記を同一のソースコードの中に共存させたくない。
->という演算子はポインタ渡しをやめれば不要になるので
呼び出し側、関数内部ともにpoint.x という表記に共通化されるので、ソースコードが読みやすくなる。
->は > 大小関係の比較演算子のように視覚的に見えやすいので、思考の妨げになる。
同様にポインタでの *演算子は、 数式中の乗算のように見えやすいので、これも思考の妨げになる。これらのわかりにくさが、参照渡しで回避される。
・呼び出し側で領域確保と初期化がされてあれば、呼ばれる側で余分な心配をしなくてよい。
・NULLポインタの可能性を考えなくてよい。
値が変わらないならconst修飾子をつける
ソースコードの可読性を高め、コードの信頼性を高めるために引数にconst修飾子をつける。
心配の種が少ないほど条件分岐は少なくなり、コードは簡潔になります。
そのような簡潔な書き方ほどバグの可能性は減り、あなたの活動をより有意義なことに向けることができます。
参照渡しを使っても残る注意点
参照渡しをしている引数であって、関数の呼び出し側で変数を宣言だけしていて、領域の確保していない場合だとバグを作りこめる。
構造体の宣言は、宣言だけであって、領域の確保が行われていないことに注意しよう。
コンパイラによっては、Debugモードでは、領域の確保をしてくれる場合があるが、Releaseモードではそのような動作をしないために、Debugモードでは動作するのにReleaseモードでは動作しないバグになることがある。
(構造体の宣言と同時に初期値の代入を同時に書くと確実に領域の確保もなされている。そのような安全なコードを書こう。)
ちなみに、構造体配列の宣言の場合には、構造体配列の領域が確保される。
変更されるのは、アドレスなのか、アドレスにある値なのか
ポインタを渡すと起こりうること。
ポインタの指し示す先の値が書き換えられること。
scanf()などはこのタイプ。
ポインタの指し示すアドレス自体が書き換えられること。
void swap(int *x, int *y);
などとユーザー定義される関数はこちらのタイプ。
参照渡しにすると
参照渡しした変数が書き換えられることだけを
心配すればよくなり、
しかも const 属性をつければ
値が書き換えるれる心配がなくなるなど
意味がとても明確になる。
意味をわかりやすくするため、
引数の文字列を指すポインタの配列を示すには、次の使い方をして、**は使わないようにしています。
int main(int argc, char *argv[]){
}
int main( int argc, char **argv ){
}
付記:ポインタは使う場合でもC++11以降のスマートポインタを使うべきだ。
qiita C++11スマートポインタ入門
qiita C++ スマートポインタのパターン
C++11, C++/13 などの規格に従った書籍が多数でています。
どういった本を読んでスマートポインタを使ってください。
付記:
- 従来のC/C++が担っていた分野のプログラミングにRustが使われだしています。
- 所有権の問題も言語仕様の時点で解決しています。