last update: 2013/09/03

2004/08/26

ポインタと参照の違いについて考える

まぁなんか報告が遅れましたが実家から20日に戻ってきました。24,5は定山渓へ温泉旅行に出かけてまして、やっと今日から本腰で大学院院試の勉強に本腰が入る…といいたいところですが8月末提出のレポートが…(しかもA4で5枚)。

とまぁそれはおいておいて、実家から戻ってくる列車で台風に巻き込まれながら(私は水難の男なので、Uターンの日程を10日前に決め、その時間めがけて台風が飛んできても最近はもう驚かなくなった)、ちょっと今更ながらC++のポインタと参照の違いについてまじめに考えたんですよ。むしろそれまでの認識は

// ポインタバージョンのswap
void ptr_swap(int *x, int *y){
    int temp=*x;
    *x=*y;
    *y=temp;
}

// 参照バージョンのswap
void ref_swap(int &x, int &y){
    int temp=x;
    y=x;
    x=temp;
}

「はーい、ポインタで書けたswap関数は参照を使っても書けるんですよー、だからC++では参照で書きましょうねー」みたいなイメージがあったわけですが、参照では書けるけどポインタでは書けない、とかなら話は分かるけどポインタでも書けるんなら別にCと互換のあるポインタをつかっておけばいいんじゃー? とか思ってポインタ崇拝主義だった私は参照とポインタのどちらでもいい場面はポインタで書いてきたんですが、参照のメリットってなんだろう。

ではそもそも私が何でもポインタで書いてきたのはなぜか。文字列の長さをカウントするstrlenという標準関数がありますが、たとえば自分でそれをインプリメントするとしたらまぁ習作プログラムならこんな感じで書くかと思います。

int strlen(const char *src){
    int i=0;
    while(src[i]!='\0'){
        i++;
    }
    return i;
}

文字列の終端('\0')がでるまでループを回してるだけなんでまぁ特に説明は不用でしょうが、これだと実戦投入するには関数として遅いんですね。なぜなら、src[i]というのはコンパイル時に*(src+i)という足し算に展開されるので、whileで評価するたびに足し算を行っているので、そこがボトルネックになるわけです。これをポインタで書くとどうなるかというと、

int strlen(const char *src){
    const char *tmp=src;
    while(*tmp!='\0'){
        tmp++;
    }
    return (tmp-src);
}

とまぁこんな感じになりまして、whileでは*tmpを評価しているのでアドレス算出のための足し算がないのでループ1回あたり数クロック速いんですね。しかし、これを参照で書くというのはムリですね。なぜなら、参照はインクリメント出来ませんし(参照をインクリメントしたら値がインクリメントされますけど、アドレスをインクリメントすることはできない)、そもそも文字列の先頭アドレスという時点でポインタが絡んできますから、素直にポインタを使えば参照を使う必要も特にありません。

さて、話を元に戻してswapの話ですが、ポインタで書いたswapと参照で書いたswap、これは特に実行速度に差は出ないと思うのでパフォーマンスはほぼ同等と見ていいですが、その上で参照で書いたswapが優れている点は何か。

答えは「参照は必ずどこか実際のインスタンスとなる変数を指していなければならない」という点です。たとえば、int &x;という参照変数の定義ではエラーが出ます。なぜなら、初期化の時点で何か実際の変数を指している必要があるので、たとえばint &x=argc;のように初期化で参照に対して実際の変数が必要です。これを踏まえて次のコードを。

int main(int argc, char* argv[]){
    int a=5, b=3;
    ptr_swap(&a, &b); // 正常に実行される
    ref_swap(a, b); // 正常に実行される

    ptr_swap(&a, NULL); // 実行時エラー
    ref_swap(a, NULL); // コンパイル時エラー (can't convert int to int&)
    return 0;
}

ということで、参照を使うメリットはNULLチェックの必要がないということでしょうか。ポインタバージョンのswapでは(わざと)ポインタのNULLチェックを書かなかったのでNULLなポインタが渡されるとそのまま実行するとこけますが、参照バージョンのswapではそもそもコンパイラの引数チェックの段階でNULLを渡すとコンパイルエラーが発生するので実行時のエラーを未然に防ぐことができる、ということですね。

ということで、参照とポインタの違い、分かっていただけたでしょうか。…というかまぁ今回はホントに誰かに説明してるのではなくて(ということは時々誰かへのプライベートメッセージが含まれていることもあるのか? と自問自答)、列車でコレに気がついてひとりで感心してたとかいう話でしたとさっ。

comments powered by Disqus