C++入門 補足4
    参照(リファレンス)

 今回は参照について説明します。(最近、説明がちょっと一本調子になっているようで、少し反省しています。)ただ、すべてを網羅的に説明することはできません。私がとても大事だと思うところだけ説明します。

 参照は英語のカタカナ読みでリファレンス(とかレファランス)などと言われることもあります。これは「別名」のことです、、、って別名ってなんでしょう。

 ここでちょっと問題です。次のプログラムを実行したらどうなるでしょう。考えてみてください。

#include <iostream.h>

void func(int x)
{
    //xに5を足す
    x+=5;
}

void main()
{
    int v=3;
    func(v);
    cout<<v<<endl;
}

 まず、funcという名前の関数を定義しています。(ここでは簡単のため、クラスのメンバではない関数、つまりグローバルな関数を例にしますが、クラスの関数でも同じです。)この関数は引数に5を足すという関数です。(x+=5はxに5を足せ、という意味でした。)
 さて、mainの中では、vという変数を作り、そこに3を入れ(正確には、vを3に初期化し)、そのvをfuncに渡し、最後にvを表示させる、というものです。
 Cを知っている人は、なああんだ簡単、と思ったことでしょう。答えは画面に3と打出されるのです。8ではありません!この辺はC++のもとになったCという言語の特徴のひとつなのです。
 つまり、、、どういうことでしょう。つまり、func(v)で関数の内部に、vという変数そのものが渡されるのではなく、vの値3が(xに)コピーされて渡されるのです。CやC++ではそうなっているのです。こういうのを「値渡し」なんて言います。ちょっとかっこいいので、覚えておきましょう。コピーに5を足しても、もとのvの値は変わらないのです。

 ところが、次のようにfuncの定義を一個所だけ変えると、8と打出されるのです。

#include <iostream.h>

//intをint&にする
void func(int& x)
{
    //xに5を足す
    x+=5;
}

void main()
{
    int v=3;
    func(v);
    cout<<v<<endl;
}

 ここで、変わったのはfuncの定義で、引数がint xからint& xになったところだけです。こうすると、func(v)で、vの値3ではなく、vそのものがxという名前で関数内に与えられることになるのです。つまり、xはvと同じものになったのです。このとき「xはvの参照である」と言います。つまり、xはvの「別名」になったのです。

 ああ、こりゃ便利と思いましたか?初心者には別に感想はないかもしれませんね。こういうやり方で変数の値を変更すると、わかりづらいプログラムになるという人もいます。まあ、これはみなさんの判断におまかせしましょう。

 実は引数を参照にしてしまうひとつの利点は、効率にあります。つまり、引数が参照でない場合、渡された値をコピーする手間がかかるのに、参照の場合はその手間がないのです。
 扱っているものが上の例のようにintなんかでは、実際、コピーの手間なんかどうということはないかもしれません。(呼び出しが非常に多いと問題になるかもしれませんが。)しかし、引数がとても大きなデータ(たとえば画像データ)を表すクラスのインスタンスであった場合、しかも、その関数が何度も呼ばれる場合、コピーの手間のせいで、プログラムの実行速度が遅くなることが考えられるのです。
 それは困ります。そういうときにも、参照が使われるのです。つまり、ある(大きな)データを表すクラスをThingとします。すると、

  void func(Thing x){
    定義
  }

より、

  void func(Thing& x){
    定義
  }

の方が(普通は)速いということです。

 関数の中で何かやってそこで変数の内容を変えるのではなく、変数の内容は変えずに、何かやった結果は戻り値で関数の外に出す、という関数も考えられます。(こういう関数で参照引数を使うことに文句を言う人はいません。)たとえば、Thingのデータの大きさを計算して、それをintで戻すような関数です。このような関数では、はじめから変数の内容を変える気はないので、const宣言を使うべきです。

 つまり、

  int SizeOfData(const Thing& x){
    定義
  }

のような関数です。引数についたconstは、引数を変更しませんという意味です。このような関数は、多くの良いプログラムで使われます。(特に、グローバルな関数で、という意味ではありません。C++なんですから、クラスのメンバ関数でも多く使われます。)

 このような形の関数で、重要なものに、コピーコンストラクタがあります。(コンストラクタには戻り値はないので、その点は違いますが、、、。)コピーコンストラクタは同じクラスのインスタンスからデータをもらって新しいインスタンスを生成する関数なので、引数がconst Neko& xのようになるのは自然だと思います。(コピーなどという余分な手間なしでデータを得、これを変更すること無くインスタンスに取り込むのですから。)コピーコンストラクタがなんだかわからない人はC++入門25、26を見直してください。入門25、26ではconstの意味や&の意味を書かずにあやふやにしていましたが、もう、わかると思います。

 なお、&の位置ですが、Thing& xでもThing &xでも同じです。これは好みの問題でしょう。私は教える立場上、流れに沿っていると思える方を使いますが、いろいろ事情があってときどき変えてしまいます。

 ここまでの話は、引数に参照を使う話でした。もうひとつの重要な使い方に、戻り値を参照にする、というものもあります。たとえば、次のクラスの例を見てください。(例によって、説明のための例で、実用上の例ではありません。)

class Mono
{
    int data;
public:
    int& AccessData(){ return data; }
};

 意味がないクラスですが、まあ、がまんしてください。ここでAccessDataが参照を戻す関数です。普通と違って、&マークが戻り値にありますね。上のようにしておくと、クラスのインスタンスでAccessData関数を呼び出すと、直接dataにアクセスできるようになるのです。たとえば、

#include <iostream.h>

class Mono
{
    int data;
public:
    int & AccessData(){ return data; }
};

void main()
{
    Mono one;  //oneというMonoを作った
    one.AccessData()=5;
    cout<<one.AccessData()<<endl;
}

 これを実行した結果はなんとなくわかると思います。5と画面に表示されるだけです。ここで、注意してほしいのは、mainの2行目の意味です。AccessDataはdataを戻しているので、そのdataに5を代入して、、、もちろん、それで良いのです。
 ただ、繰り返し注意しますが、AccessDataの戻り値はintではなく、int&です。これによって、dataと同等のもの(参照)が戻されるているのです。もし、int&ではなくただのintであった場合、AccessDataの戻り値は、普通の値(とはいえここではdataは初期化されていないのででたらめな値ですが)であって、変数でないので、新たに値を代入できません。したがって、mainの2行目でエラーが出てしまうはずです。

 戻り値に参照を使うひとつの理由は上のようなテクニックを使いたいからです。しかし、賢いみなさんはもうお気づきのように、上のクラスは無用そうという以上に、おかしなクラスです。というのは、せっかくカプセル化したはずのdataをわざわざ関数を使って、外部にさらしているからです。
 ちょっと初心者には難しいと思いますが、カプセル化したデータを外部にさらすのは、よくよく考えてから実行するべきです。上のようなクラスなら普通はGetDataとSetDataを作る方が、自然で無難だと思います。しかし、他のプログラマとの連携や過去(C)のしがらみやら、いろいろな事情で、上のようなことをするときもあります。

 ちょっとネガティブな言い方になってしまいましたが、もちろん、きちんとやれば、良いコードになります。具体的な話は中級向きの本で読んでください。

 戻り値に参照を使うもうひとつの重要な例は、「演算子の多重定義」というもので現れます。しかし、「演算子の多重定義」そのものが新しい話なので、今日はこの辺にしましょう。

C++入門の目次
HPの総目次