C++によるプログラミング入門補足3
参照(リファレンス)について

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


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

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

//ref_sample0.cpp
#include <iostream>
using namespace std;

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

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

 まず、funcという名前の関数を定義しています。(ここでは簡単のため、クラスのメンバではない関数、つまりグローバルな関数を例にしますが、クラスの関数でも同じです。)「x += 5;」は「xに5を足せ」という意味でしたね。
 mainの中では、vという変数を作り、そこに3を格納し、そのvをfuncに渡し、最後にvを表示させる、というものです。
 Cを知っている人は、なああんだ簡単、と思ったことでしょう。答えは画面に3と打出されるのです。8ではありません!この辺はC++のもとになったCという言語の特徴のひとつなのです。

 つまり、、、どういうことでしょう。つまり、func(v)で関数の内部に、vという変数そのものが渡されるのではなく、vの値3が(xに)コピーされて渡されるのです。CやC++ではそうなっているのです。こういうのを「値渡し」なんて言います。ちょっとかっこいいので、覚えておきましょう。コピーに5を足しても、もとのvの値は変わらないのです。

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

//ref_sample1.cpp
#include <iostream>
using namespace std;

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

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


おおー。

 vの値がfuncで変わったのは、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){
  定義内容

の方が(普通は)速いということです。(引数として与えるオブジェクトが、あらかじめ生成されていないと、それを生成する手間がかかり、効率的ではなくなります。)

 しかし、funcの中で、引数の内容を変えるつもりはない場合もあります。つまり、引数のデータは、それを使うだけで、変更するつもりがない場合です。そのようなときは、

void func(const Thing& x){
  定義内容

と書くわけです。こう書くと、「定義内容」の中で、xについて、const指定されていない(つまり、宣言・定義で後ろにconstのついていない)メンバ関数を呼び出すことはできなくなります。constについては、補足1、補足2も参考にしてください。


補足 C++入門6

 C++入門6の最後の補足で、neko3.cppのNekoクラスを

class Neko
{
private:
    string name;
public:
    Neko(string s) : name(s){}
    void naku(){
        cout<<"にゃあ。俺様は"<<name<<"だ。"<<endl;
    }
};

のように書くと、Borland C++コンパイラは

「値でクラスを渡す引数を持つ関数はインライン展開されない」

という警告します、と書きました。
 これは、Borland C++が「コンストラクタをインラインにできない」と判断し、警告を出しているのです。その理由は、コンストラクタの引数がクラス(今の場合、文字列を表すstring)のオブジェクトになっているからです。
 上に書いたように、C++の関数では、引数の値が仮引数にコピーされます。しかし、クラスのオブジェクトには「メモリをたくさん使う大きなもの」もあるでしょう。そのようなオブジェクトをコピーするのは効率が悪いので、Borland C++は、「引数の型がクラスの関数はインラインにしない」と判断しているのです。これは、コンストラクタであっても、別のメンバ関数であっても同じです。
 しかし、関数の引数を参照にすると(つまり、参照渡しにすると)、インライン化をし、警告を出さなくなります。それは、つまり、たとえば、次のようにすればよいわけです。

class Neko
{
private:
    string name;
public:
    Neko(const string& s) : name(s){}
    void naku(){
        cout<<"にゃあ。俺様は"<<name<<"だ。"<<endl;
    }
};

 コンストラクタの仮引数に、constを付けたのは、警告とは直接関係ありません。sの内容を変更するつもりがないので付けました。


目次のページ