C++のポインタ渡しと参照渡しの使い分け

社会人1年目、会社で部署に配属された初日に、
上司に「C++は全然わからないです!」って言ったら、
「お前がわかってないのはC++じゃなくてコンピュータの基礎だ」
と言われたくーむです。こんばんは。

今日は 初心者 C++er Advent Calendar 2015の8日目(7日目?)の記事です。
C++歴は1000行くらいの超初心者です。


CodingameとかHAL研プロコンで使ったことがあるけど、
仕事ではなかなかガッツリ書く機会がなくて、毎日数行足したり減らしたりしてだけなので、
なかなかC++力つきません。

ポインタコワイ

Python界からきた私にとってポインタは超怖い子です。
そもそも変数の前に記号が付いてるだけでコワイ。(PerlもPHPもコワイ)

そんなこと言ってたら友人がこの本を貸してくれました(多分もう売ってない)

1冊まるごとポインタについて語ってて、これをよんだらだいぶポインタと仲良く慣れました。
hoge[1] と 1[hoge]は同じだということとかがわかって面白いです。
でもポインタの説明はここではしません。できません。

C++の参照

pointerで同じようなことができそう(?)なのになんで参照ができたんだろう?と思ったのですが、
C++の参照は、演算子のオーバーロードをするためにできたそうです。
たしかに、演算子がポインタ返してきたらつかいにくいです。

class String {
 char& operator[](int index); 
};

参考: A History of C++: 1979− 1991




ポインタと参照についてはまだよくわからないのでさっさと本題にいきます。

私がC++を書いてて困ったのが、ポインタ渡しと参照渡しの使い分けです。
基本的にどっちでも書くことができてしまうため、どうするのがよいのかわからなかったのです。

ポインタ渡しと参照渡しの違いを考える

swap関数をポインタと参照を使ってそれぞれかいてみます。

// pointer
void swap_ptr(int *a, int *b) {
  int tmp = *a;
  *a = *b;
  *b = tmp;
}

int main() {
  int a = 10;
  int b = 20;
  swap_ptr(&a, &b);
}
// reference
void swap_ref(int &a, int &b) {
  int tmp = a;
  a = b;
  b = tmp;
}

int main() {
  int a = 10;
  int b = 20;
  swap_ref(a, b);
}

参照は、関数で渡す時呼び出し先で
明示するので、
呼び出し元では普通に実体を渡すようにかけます。

また、参照は普通の実体と同様にオブジェクトにアクセスできます。

次のようなプログラムを考えてみます。

#include<iostream>
void f(int a, int &b, int c) {
  b += 10;
}

int main() {
  int a = 10;
  int b = 20;
  int c = 30;

  std::cout << a << " " << b << " " << c << std::endl;
  f(a, b, c);
  std::cout << a << " " << b << " " << c << std::endl;
}

このプログラムを実行すると、結果はこうなります。

10 20 30
10 30 30

f(a, b, c)を呼び出すと、bのみが変わって帰ってきました。
でも呼び出し元( f(a, b, c) )を見た時に、読んでる人はbだけ変わると予想するでしょうか?

これがポインタであれば、

#include<iostream>
void f(int a, int* b, int c) {
  *b += 10;
}

int main() {
  int a = 10;
  int b = 20;
  int c = 30;

  std::cout << a << " " << b << " " << c << std::endl;
  f(a, &b, c);
  std::cout << a << " " << b << " " << c << std::endl;
}

となり、bだけ違う感じがでます。

つまり、参照は普通の実体とほとんど同じ様に使えるかわりに、
参照であることがわかりづらい
と言えるかなと思います。

ポインタ渡しと参照渡しの使い分けについて

そんな感じで、これまでをふまえて、私は
参照渡しは値を変更しない時に、必ずconstと一緒にしています。
(参照渡しではnullは渡せないので、値がnullになる可能性があるものは変更の可能性がなくてもpointerで渡しましょう。)

Google C++スタイルガイド 「リファレンス引数」の章にもそのように書いてありました。

他にいろんなスタイルがあるかと思いますが、
上に書いた様な理由をふまえて、私は今のところこの使い分け方で納得しています。

反論がある方は(理由を含めて)是非コメント下さい!

まとめ

私は参照渡しには必ずconstをつける派です!!!!!!!!!!!

さいごに

もうちょっと真面目に書こうと思ってたんだけど、やってるスマホゲームのイベントが今日までなので、
このへんにしておきます。C++力高められるようがんばっていきたい。

明日は yumetodoさんで、「Clang with Microsoft CodeGenがでたので試す」ですっ。
よろしくお願いします( o・ω・)ノ