参照はポインタと比べるべきではない
協力会社のデキるプログラマを次々と切り、代わりにあまりものの正社員が投入されてしまった現在の我が開発チームのレベルは低く、殆どプログラミング専門学校のような様相を呈しています。可愛い男の子が「わかりませーん」というのは萌えますが、30台後半のおじさまが「わかりませーん」というのは殺意が沸きます。
自称 「STLは使ったこと無いけど、C++ 自体は解ってます」な方なのですが、「C++出来ます」の自己申告は危険です。たとえば、曰く、参照がわからない、ですとか。・・・・テケスタ。テケスタ。
ただ、参照には教え方にコツがあるのではないかと思いました。
Cのポインタが文法も概念も引っかき回していった後だけに、他の言語では何でもない「参照」がこんがらかりやすいのは事実です。そういう風に思い直して、こんな解説をしてみました。
* * *
C++ の参照と対になるのは、ポインタではなく、通常の変数です。C++ では、C の変数を引き継いだ「変数」と、新しい概念(モデル)に基づいた変数である「参照変数」の二つが共存しています。
(リプレースではなく共存してしまうあたりが、拡張言語たる C++ の難しさ・面白さですね)
まず C++ の通常の変数ですが、これは「箱モデル」に基づきます。
C において変数は、コンテキスト(関数のローカルスコープとか)ドデンと結合された箱 で喩えられます。絵にするとこんな感じ。
このモデルの特徴は、「値を変数に格納する」と捉えるところです。コンテキストが破壊されると箱も破壊され、それに伴い値も破壊されます。そこまではいいのですが、格納する以上、箱には値を収めるに十分なサイズが要求されます。さらには、一つの値は一つの箱にしか仕舞えません。ここら辺が C++ で新たにサポートされたプログラミングパラダイム、OOP には都合がよくないのです。
そんなわけで、C++ で新たに追加になった「参照変数」ですが、Cの変数の「箱モデル」に習うのなら、こちらは「フックモデル」とでも言いましょうか。
参照変数は、コンテキストにねじ込まれたフックのような存在で、風船のようにプカプカ浮かんだ値をフックに「紐づけする」・・と言う風に喩えられます。ようするに、こんな感じ。
このモデルの特徴は、「値を変数に紐付けする/束縛する」と捉える所です。コンテキストが破壊されると、値は束縛を解かれ、自由になります(・・・ということは、ガベージコレクタが居ないとメモリーリークが発生します。)。
フックに引っかけるだけなので、値のサイズは全然考えなくても大丈夫です。極端には「どの様な型の値でも束縛できる変数」だって出来ちゃいます(動的言語では普通です)。さらに、一つの風船を複数のフックに紐づけしても何ら問題がないのも嬉しいところです。
以上のように、(普通の)変数と参照変数は 立脚するモデルこそ違うモノの、どちらも同じ「変数」のバリエーションです。だから文法上も 統一感をもって扱えます。(変数 target_? に注目してください)
int hoge = 10; int target_v = hoge; // 左辺値として扱われるときは、変数自体 std::cout << 3 + target_v; // 右辺値として扱われるときは、束縛された値
int hoge = 10; int & target_r = hoge; // 左辺値として扱われるときは、変数自体 std::cout << 3 + target_r; // 右辺値として扱われるときは、束縛された値
変数に値を束縛する際に、値側に妙な演算をする必要はないですし、変数に取得された値を取得する際も同様です。
* * *
ただ、この「参照変数のメリット」みたいなことは、C でも実現することが出来ました。そう、ポインタを使えばいいのです。けれど気をつけたいのは、「参照」とは変数の一種である一方で、「ポインタ」とは値の一種なのです。
C には 他の値をポイントする「ポインタ値*1」というへんてこりんな値があります。値なので当然変数に格納することが出来ます。ポインタと、それを格納した変数(ポインタ型の変数)と、ポインタに紐付けされた値の関係はこんな感じ。
参照と同じことが実現出来ちゃうテクニックですが、気をつけなければイケナイのは、ポインタ値は値なので、「束縛者自身」と束縛されているモノを、文脈に合わせてよろしく解釈してくれるようなことは期待できません。
たとえば、
ポインタ値 = ある値;
はコンパイルすると型不一致エラーになります。
このようなとき、「ある値」を元に「ある値 のポインタ値*2」を作るための演算が必要です。また「ある値」が欲しいときは、「ある値のポイント値」からそれを求める演算もする必要もあります。
int hoge = 10; int * target_p = &hoge; std::cout << 3 + (*target_p);
余談ですが、値であるメリットもあって、値は普通に演算することができます。だから「ポイント値をずらす」「ポイント値を足す」などもできちゃいます。でも参照はあくまで変数なので、そう言うことは出来ません。
* * *
というわけで、コツとは単に、ポインタと比べてはいけない。普通の変数と対比する・・というだけでした。むちゃくちゃ煽っておいて、恐ろしく当たり前なことだという...期待させてしまってたら申し訳ないです。
こんな話、やっぱり新人にするような話ですが、参照すら解らなくても 「C++ を使える」といって生きてこられてしまうのが コの業界の現実ですね――すみません、結局愚痴エントリでした。