「C++再考」の訳注集5
  第6、7章安全性と速さと、、、(私見)

 また、更新が遅れてしまいました。リアルタイムで読んでくれているみなさん、毎度のことですが、どうもすみません。実は、私事ですが、引っ越しをしました。その関係で、忙しくなったのと、電話が不通になり、最後に引越しの際にパソコンのモニタがいかれてしまった(涙、涙)ので、更新できなかったのです。(新しいモニタを買いました。それと関係ないですが、外付けのハードディスクがひとつ復活しません。これらの不調は、もともと調子が悪かったのが、引越しで止めを刺された、という感じです。でももちろん、引っ越したこと自身はとても良かったです。それから新しいモニタは○○トロンというやつでダンパー線とかの影がちょっと嫌ですが、抜群の映りの良さで感動しています。まるで新しいパソコンを買ったようです。)

 私事で、、、というついでに、今回は私の意見を書きます。わざわざ「私見」と断るのは、他にも考え方があるだろうからです。みなさんは、以下の記述を(というか私のHP全体について同じですが)自分の責任において、判断してください。ね。

 その話の前に、一言。第5章では、ポリモーフィズムとメモリ管理の話がメインでした。つまり、ポリモーフィズムを活かしながら、メモリ管理(newとdelete)は楽にやりたいということでした。第6章、7章では、第5章のコードで予想される「不要なコピー」を減らす話になりました。これには「使用カウント」という手法が使われます。
 ここで、ちょっと初心者に注意したいのは、第6章は第5章を受けているようで、実はポリモーフィズムのことは忘れています。使用カウントはポリモーフィズムとは関係の無いもので(もちろん一緒にもできます)、第6章は使用カウントの説明なのです。そのため、第6章のコードでポリモーフィズムをやろうとすると、あんまり簡単ではないと思います。続く第7章で、ポリモーフィズムと両立しやすい、しかし、その分ちょっと難しい方法が説明されます。本文にもそう説明されていますが、一応、念のためのコメントです。
 なお、第7章でもポリモーフィズムの例にはしていません。(できる、と書いてはあります。)しかし、第5章と第7章を読んで、自分で考えればなんとかなるはずです。興味のある人はぜひやってみてください。
 ところで、第7章のコードをちょっと見ると、ハンドル同士の代入では、不要なコピーがされなくなります。前回の訳注の「猫」のような例に適用すると、コピーが一回減ることになります。しかし、「猫」のような使い方なら、実体のクラスからハンドルを作るときには、Handle::Handle(const Point& p0):u(new int (1) ), p(new Point(p0)){ }などとなっているため、この分のコピー(コピーコンストラクタの呼び出し)は回避されません。(Pointが実体のクラス、Handleがハンドル、という意味です。それからPointは小さいデータなので、本当は使用カウントを作るほどのことはないと思います。これはあくまでも練習用です。)
 コピーがひとつ減ったから良しとするか、ここもコピーがなくなるようにしたいか、あるいは前回の「猫」のような素朴な使い方はしないようにするか、、、はプログラマが決めることで、一般論は無いと私は思います。もし、扱っているデータがとても大きくて一回でもコピーを減らしたい、しかし使い勝手の良さは失いたくない、、、なら、工夫すべきでしょう。第7章にはそのような場合まで書いてありません。それは、いたずらにコードを複雑化させ、読者のやる気をなくさせないための著者の心使いであるように思います。
 たとえば、私なら、(Pointに相当するしかし大きな)一度作った(ロードした)データはなるべく移動させずに確保しておいて、コピーコンストラクタ等では、そのデータのアドレスを得るようにするでしょう。しかし、これはうっかり元のデータを移動させたり、また、変更した場合の対処をちゃんと考えなくてはなりません。ちゃんとやると第6、7章のコードに似たものになるでしょう。しかし、こうなると、データの構造に従って、特別なコードを書いた方が良いかもしれません。あとはみなさん次第ということでしょう。

 

 さて、みなさんはよく「C++は安全」とか「C++は速い」ということを聞くと思います。何にくらべて安全なのか、何にくらべて速いのか、うっかり書くと非難のメールが轟々ときますので、なるべくあたりさわりなく言うと、まず、ひとつめは「C++にはクラスがあるのでCにくらべて安全なコードが書きやすい」ということだと思います。Cを知らない人はクラスのないC++を想像してください。危険そうですね、、、なんて思えませんか?
 たとえば、クラスがあるとコンストラクタやデストラクタが使えます。コンストラクタ、デストラクタを面倒と思っているうちは、まだまだ初心者です。コンストラクタでデータを初期化し、デストラクタで後始末をする、、これはとてもすばらしいしくみなのです。というのは、コンストラクタでデータの初期化をするので変なデータがはいることを阻止できるし、デストラクタは自動で呼び出されるので、プログラマが後始末のし忘れやし過ぎをしないですみます。

 「C++は速い」はちょっと微妙です。まず、Cは「速い言語」だと言われています。(何にくらべて?と聞かないでください。この話は一応認めてください。^^)C++はCを内に含んでいるので、Cと同程度には速いのです。(なんか「当社比」みたいな話ですが、)しかし、工夫によっては「Cより速いこともある」とC++のプロは言うのです。(以下Cの話がでますが、主に比較の対象として選んだだけなので、Cを知らない人は気にしないでください。)
 理論的にはC++はCと同等のはずです。しかし、コードを書いていてあまりに複雑になると、プログラマはそんなコードは放棄せざるを得なくなるはずです。ところが、C++にはクラスという構造物があるので、コードがより単純になるのです。
 たとえば「使用カウント」が良い例だと思います。このような働きをCで書くことはもちろん可能です。とすれば、(たぶん)そのコードとC++のコードは同等のものでしょう。(場合によってはC++より速いでしょう。)しかし、Cで「使用カウント付きのデータのメモリ管理」をするのはちょっと面倒で、間違いも多くなります。
 世の「達人」という人たちのことは知りません。しかし、普通にプログラムを書いている人は、あまり面倒なことを間違えずにはできないので、「C++でなら書けるコードがCでは無理」ということになるのです。(繰り返しますが、世界に何人の達人の話をしているのではありません。)「C++再考」の著者はとても偉い人ですが、それでもCでは文字列の処理がとても大変で自信をもってプログラムできなかったと第1章に書いています。(この章は私にはとても勉強になりました。ほとんどすべてのC++の教科書に書いてある文字列クラスにはこんなエピソードがあったんですね。)
 C++はあまり難しくならないので、普通のプログラマにも「速いコード」ができるのです。たとえば、大きいデータを扱う場合、使用カウントがあればあきらかに「速い」ですし、そういうコードをC++では簡単に書いたり、使ったりできるわけです。
 しかし、当然のことと、、、私には思えるのですが、安全性と速さは基本的には相反するものです。クラスの良い点には、コンストラクタ、デストラクタに限らず、いろいろな関数に「安全チェック」つまり「if文」を入れられるということもあります。(こうすると、一度書いた安全装置が、いつも自動で実行されるわけです。)ところが、こういう安全装置は、そのための実行時間が必要ですから、速さは犠牲にしているのです。

 もうお分かりのように使用カウントはデータが大きいときに威力を発揮します。しかし、データが小さい場合は、ifのチェックなどで、無駄に遅くする要因にもなるのです。
 ちょっと唐突ですが、「継承熱」ということばあるそうです。継承を覚えると、何がなんでも継承を使ってみたくなるという、、、ああ、自分がそうだった、、、とニヤニヤしている人も多いでしょう。私はそうでした。それをまねると、「使用カウント熱」もあるかもしれません。私は一時期、何にでも使用カウントをつけようとしたことがあります。そして、さあ、速いコードができたぞ、、、と思って実験すると、前と大差が無い、、、ときには遅くなってすらいる、、、ということもありました。とほほですね。
 使用カウントは確かに大きなデータには有効です。しかし、いつでもというわけではないのです。

 安全性と速さは相反する、、、と書きました。でもC++は安全で速い、、、。えー、私の意見を整理すると、こうなるでしょう。

1.C++はCを含んでいるから、当然速いと言われるCと同じコードも書ける。
2.クラスにより安全性(構造性)を増し、Cでは複雑なコードをC++では比較的簡単に書ける。そのために「速いコード」を簡単に書くこともできる。
3.この「速いコード」とは、「C++の安全性を利用して書いたコード(Cでは複雑すぎて、普通のプログラマは間違えるか手を出さないコード)」で、往々にして「急がば回れ」的な「速いコード」であることも否めない。
4.ならば、コードの再利用を考えながらも、ケースバイケースでコードを考える必要があるだろう。
5.こういう現実主義こそ、C++の基本理念だと思う。
6.なお、本当に速さが必要なのは、プログラムのほんの一部であることも多い。この部分だけ、エラーチェックを徹底的にした「本当に速いコード」を使って、多の大部分では、安全なコードを使う、、、というのが望ましいのではないか、と考える。

以上です。

 ちょっと出てきましたが、再利用は「普遍性」、ケースバイケースは「特殊性」を意味しますね。C++とは「安全性と速さ」、「普遍性と特殊性」というように、ときに矛盾し、ときに補完し合う複雑な問題に対して、プログラマが自分で判断してコードを決めていける言語なのだと思うのです。(C++万歳!)

 今回は言いたい放題で、失礼しました。

前のページ
後のページ
訳注の目次
総目次