ポインタと参照の棲み分け

 さて、今回がポインタ編一応の最終回です。「ポインタの具体的な使い方」は他の良書を参考にしてもらうとして、今回は「ポインタと参照をどう両立していけばいいのか」ということに注目してみましょう。

ポインタ派VS参照派
 とかいうものが実際に存在するかどうかは未確認だけど、でもじっさいに「ポインタは使いたくないな」「参照は使っちゃダメでしょ」みたいな意見を持ってる人は結構いるみたいです。要約すれば「見やすさや打ち込みやすさを考えてどちらかに統一したいけど、実際にはどちらも使わざるを得ない」ということなのでしょう。
 バグを減らすコツとして「いろいろな方法がある場合にはひとつに統一する」というものがあります。参照とポインタを混ぜて使ってもそれほど危険ではありませんが、明らかに可読性は落ちますし、コーディング時のミスも増えるでしょう。
 そのため、実際には統一したい、でもMFCの中では混ざって使われているし、部下のコードでは参照がよく使われている、できれば両方のいい部分を使いたい、そういう現実が「結局どちらも使う」という選択になっているのでしょう。

 ポインタはシステムの根底に関わっていて、フレキシブルな機能を持っています。つまりポインタは、これひとつあればたいがいのことは事足りるだけのポテンシャルを持っているから、とても便利だということになります。ですが、その分使いこなすには深い部分まで理解する必要があって、それが「初心者キラー」とまで呼ばれ、また上級者にとっても、ちょっと危なっかしい代物というイメージを持たせています。
 参照はポインタが持つ機能の中から「変数のエイリアス」の部分に特化されたものです。特に関数の引数に使用する場合、とても安全に「参照渡し」が行えます。また他の言語との親和性も高く、C++への移行者にとっては違和感なく使える機能です。その反面、ポインタのようなフレキシブルさは持ち合わせていません。実際「関数の参照渡しくらいにしか使えない」と言えるのではないでしょうか。もちろん演算子のオーバーロード時など他にも使う機会はありますが、使う機会は「参照渡し」の方が圧倒的に多いでしょう。

 これを、簡単なたとえ話に置き換えてみましょう。

ナイフとカッター
 ちょっと物騒かもしれませんが、ポインタを「ナイフ」に、参照を「カッター」に置き換えてみましょう。

 ナイフはとても便利な道具です。「ツタを切る」、「木を削る」、「毒虫を刺す」、「土を掘る」など、さまざまな使い方ができます。ジャングルのようなサバイバルな環境では、ナイフひとつあるだけで様々なことができるようになります。

 と、ここで場所をオフィスに変えてみましょう。ナイフを使う機会はあっという間になくなります。使えるとしたら紙を切るくらいでしょう。
 しかも、オフィスには「紙を切るために特化されたアイテム」、カッターがあります。ナイフはなんでもできる代わりに、ちょっと危険な代物です。扱いを間違えて手を切ったりしかねませんが、カッターはそれを防ぐために刃をちょっとずつ出せたり先があまり尖ってなかったりしています。それに、ナイフは幾分大きいため、定規を当てて紙を切るのには不便でしょう。カッターの方が紙を切る分には向いていると言えます。

 さて、ここでまた場所をジャングルへと戻してみましょう。ここで手に持っているのがカッターだったら、どうしましょう。カッターは「歯を折る」ことで切れ味を保つため、当然刃がすごく折れやすくなっています。木を削ったり土を掘ったりしたらすぐダメになってしまうでしょう。それに先が尖っていないので危険な虫を刺し殺したりもできません。つまりカッターだけ持っていてもジャングルでは生き残れないのです。

WindowsとMFC
 では話をプログラミングに戻しましょう。現実では「ジャングル」と「オフィス」がいきなり入れ替わったりしません。でもプログラミングではこのふたつが隣り合わせに存在しています。つまり、「オフィス」は「ジャングル」の中に作られている、ということです。そしてしばしば、オフィスからジャングルへと出なければならないのです。
 もうおわかりだと思います。ここでの「ジャングル」は何もない環境、Win32SDKのみのプログラミング環境で、「オフィス」はいろいろなものが揃った環境、MFCやATLに守られた環境です。

 普通にプログラミングをしている場合、そこは環境の整った「オフィス」でしょう。MFCやiostreamのようなクラスライブラリ、ATLやSTLのようなテンプレートライブラリ、そのほか各種コンポーネントが揃い、至れり尽くせりの世界です。こういった環境では参照のような、特定の機能に特化されている安全なものが奨励されます。
 ところが、この「至れり尽くせり」の環境でも満足できない場合が出てきます。「整っている」はずの環境に必要なものがなかったり、また満足した結果を出してくれなかったりした場合、あなたの取る行動はひとつです。

 そう、あなたはジャングルへと赴かなければならないのです! ジャングルへと出て、自分の力で新しいオフィスを作ったり作り直したりしなければならなくなるのです。APIやCOMインターフェイス、各種アルゴリズムを使いやすくするための関数やクラスを作る必要が出てくるわけです。
 こんなとき、オフィスにあるカッターではあまりにも心許なすぎます。そして、こういう時に頼りになるのが、ナイフ、つまりポインタのような汎用性の高いものです。ポインタを使って配列やITEMIDLISTを操作し、それをクラスとしてパッケージングすることで、新しいオフィスが生まれ、そこでの快適な仕事が生まれるわけです。

 このとき、あなたに奇妙な誘惑が生まれることでしょう。そう、「そのままここで仕事をしていいんじゃないか」というものです。たとえジャングルの中ででも、泥まみれでも、ちゃんと仕事はできます。ナイフがあればカッターは要りませんし、それ以外の様々なこともできます。
 でも問題は、そこで仕事をするとき、同僚もそこで仕事をしなければならないことになります。ジャングルの中ではなんでも1から作らなければなりません。もし作ったものを取っておいたら、それは「オフィス」になってしまいます。オフィスを作ったら、そっちの方が便利だと言って同僚が行ってしまうかもしれません。
 ジャングルは言わば、ウィンドウプロシージャにすべてのコードを書き込むようなものです。そこには「再利用」という概念はありません。そして、再利用をし始めれば、楽をするためにそれを使いまわしていくことになり、かくしてオフィスが生まれ、ジャングルとオフィスの間に壁が生まれるわけです。

オフィスとジャングルでの分業
 さて、実際にみなさんはおそらく、オフィスとジャングルを行ったり来たりしていることでしょう。でも今見てきたように、その間にはちゃんとした「壁」が存在します。再利用化を考えたクラスやコンポーネントがオフィスの便利な道具となることで、ジャングルに行く必要性をなくせるわけです。
 ここに、参照とポインタを混ぜ合わせない秘訣があるのではないかと思います。オフィス向けの道具はオフィスの中で使い、ジャングル向けの道具はジャングルの中で使えばいいのです。つまり「棲み分け」です。

 ポインタや配列を駆使したアルゴリズムや、ShellAPIやCOMAPIの操作などの「ポインタ必須」のものは、関数やクラス、コンポーネントの中に封じ込めてしまいましょう。そして、これらを使う時にはポインタを使わず参照を使うようにすれば、簡単に「棲み分け」することができます。
 この「棲み分け」はとても有効に働くことでしょう。ポインタ関係のバグが発生する場所は当然限られますし、別々に作っていけば頭の中でも整理しやすくなります。可読性も高くなり、メンテナンスも楽になるでしょう。

 実際、こういったことはすでに行われているわけです。クラスや関数のパッケージングはこういうことのためにも重要に機能します。Cランタイムライブラリは、さらに細かい作業をしていますが、それを関数という形に封じ込めて、簡単に操作できるようにしています。
 もしあなたの他に人手があるのなら、こういったことを複数人数でするのが効率的な方法でしょう。あるパートはクラスライブラリを作り、あるパートがそれを組み立ててアプリケーションにする。理想を言えば、VCでActiveXコンポーネントを作り、それをVBで組み上げられれば、完全な分業ができるようになるでしょう。

MFCと参照
 ところが、ここで問題が出てきます。それはMFCです。MFCは参照とポインタをぐちゃぐちゃに混ぜて使っているため、とても混乱します。
 まず参照ですが、MFCではポインタよりも使われていない傾向にあります。一番使われているものはCStringを引数に取るもので、これはCStringが「文字列を受け取ること」が苦手なためです。
 でも、CStringを含めて参照を引数に取るメンバ関数は、たいがいポインタを使ったメンバ関数がオーバーロードされているので、参照を使いたくない場合はそちらを使う方がいいでしょう。

 ただ、一部のメンバ関数の中には参照を使ったものしかないものもあります。こういったものを使う場合には、参照を使わざるを得ないでしょう。ただ、引数として渡すだけなのでそれほど混乱はしないと思います。

MFCとポインタ
 前述したように、MFCではポインタがメインです。そのため、ポインタを使わずに済ませるというのは不可能でしょう。特にクラスのやりとりにポインタを使用しています。MFCは「APIのラッパークラス」としての意味合いが強く、ハンドルそのものも一種のポインタということもあって、その方が自然と判断されたのかもしれません。
 中にはCWnd::CreateControl()ように参照とポインタをごっちゃに使っているものもあります。また、読み込み用のポインタなのにconstを使っていないものも多く(もしかしたら裏で処理をしているのかもしれません)、仕様としてはちょっと不安な所も多く見受けられます。でも、やっぱりMFCを使う上で、ポインタは不可欠でしょう。

 現実問題として、MFCは「便利なクラスライブラリ」としよりも「APIのラッパークラス」としての部分があまりにも強すぎるのかもしれません。けれども、難しいウィンドウズプログラミングを楽にこなすためのクラスライブラリ、としての側面もあって、そのために仕様が混乱しているのかもしれません。

参照から解説したワケ
 さて、最後になりますが、この「ポインタ編」の趣旨、みたいなものを書いておこうと思います。

 「Codian」は、Visual C++ユーザーのための講座です。それをふまえ、VCユーザーにとってポインタを一番理解しやすいであろうと考えて書いたのが、この「ポインタ編」です。
 VCには、すべてが一通り揃っています。何でもできるMFC、昔ながらのCランタイムライブラリ、データ格納に便利なSTL、便利なツールなどが買ったときから使える状態になっています。また、以前に比べてVCの本がとても多く発売され、本屋に行けば多くの情報が得られます。もちろん、Codianを含め、多くのホームページにも解説があります。まさに至れり尽くせりです。
 こんな環境において、「ポインタ」は「必要性が低い」と言わざるを得ないのではないでしょうか。

 MFCでは「引数に*が付いていたら&を付けて渡せばいい」くらいの知識で十分な領域です。ポインタのインクリメントとか、ホントは整数値なんだとか、そういったことは知らずにいてもそれほど問題なく、また問題の出るコードを書く機会もない、そういう環境です。
 CStringを使えば文字列操作ができ、配列はSTLのvectorが使え、ダイアログアイテムもクラスウィザードが面倒を見てくれる、そういう「整ったオフィス」が、VCには付いてきます。

 ところが同時に、VCはある意味「もっともローレベルでのコントロールを行える唯一のツール」でもあります。VBのような他の開発環境では作成できないものを作り、操作できないものを操作する、それがVCの役目でもあるのです。
 そういったものを作るときには、当然ポインタが不可欠で、それ以外の様々な知識も持たなければなりません。ジャングルでオフィスを作り上げることは、まさにサバイバルです。

 VCを使い始め、勉強していって初めて当たる壁は「ここ」にあると思います。これはポインタだけではなく、MFCとSDKの違い、VCが覆い隠してきたウィンドウズの中身など、そういった「わけのわからないもの」に触れる恐怖が、ここにあります。
 こういった場合、理解しやすく、使いやすく、そして実際に使う必要性のあるものから紹介していった方がいいと考えました。そこで、ビット和などで必要な変数の中身を説明し、引数を通して値を返せる参照を紹介し、最後にポインタを見てきたというわけです。

 こういった趣旨があるため、たとえば「UNIXでCを学ぶ」という方にはおそらくあまりお役には立てなかったと思います。けれども、「VCからC言語を学び始めた」という方には、ポインタを理解する上では結構有益なんじゃないかなぁと思います。
 これからもポインタについて勉強することはたくさんあると思いますが、そんなとき、この「ポインタ編」がお役に立てたら幸いです。

 と、最後におまけ。次のページに「ポインタQ&A」があるのでそちらもどうぞ。

(C)KAB-studio 1998 ALL RIGHTS RESERVED.