2010-09-18

多くのプログラマは言語を表面的な理解だけで使っている

一般のプログラマの多くは、プログラミング言語というものを、ごく浅い表面的な理解だけで使っている。これは、いわゆる「入門書」によるところが大きい。入門書は、言語をできるだけパターンで教えようとする。かくかくしかじかの場合には、とらとらうまうまのように書いておけばいい、などといった具合だ。

たとえば、配列の全要素や、aggregateの全メンバーをゼロで初期化したいとする。多くのC++プログラマは、以下のように書く事であろう。

int a[100] = {0} ;

このコードは、正しく動く。配列aの要素は、すべてゼロで初期化される。しかし、C++という言語を考えた場合、{0}と書く必要はない。空の{}で十分なのである。

int a[100] = {} ;

では何故、多くのC++プログラマは{0}と書くのか。それは、多くの参考書が、そのように書いているからに過ぎない。大多数のC++プログラマは、どうも{0}を、特別な文法か何かのように錯覚しているようである。

そもそも、以下のコードはどういう意味なのか。

T x = {0} ;

これは、「Tの最初の要素あるいはメンバーを、0で初期化し、その他をすべて、staticストレージと同じ方法で初期化する」という意味である。staticストレージは、明示的に初期化子を欠かなくても、必ずzero-initializedされる。そのため、このような意味になる。たとえば以下のように書けば、

int a[3] = {100} ;

「a[0]を100で初期化し、a[1]とa[2]はゼロ初期化する」という意味になる。

しかし、それだったら最初から、すべてstaticストレージと同じ方法で初期化する意味で書けばいいのだ。

int a[1000] = {} ; // すべての要素をstaticストレージと同じ方法で初期化(すなわち、ゼロ初期化)

すでに述べたように、なぜ、いまだに多くのC++プログラマが{0}と書くかというと、多くの参考書がそう書いているからだ。なぜ、多くの参考書がこう書いているかというと、C言語では、空の初期化リストは書けなかったのだ。

int a[5] = { } ; // C言語ではエラー、C++ではOK

これは、C++による改良である。C言語の文法は絶望的に汚い。近年のC言語の文法がいくらかましになっているのは、C++のおかげであると言える。ところが、C99ですら、いまだに空の{}を認めていない。C99は、C++なら、わざわざ言語機能にするほどでもないようなものまで、言語機能にしていたりと、かなり迷走している。また、次のC言語規格も、悲惨である。C言語に未来はない。

配列や構造体の中身をゼロで初期化したいというのは、プログラミングにおいては、かなり一般的な要求であった。そのため、{0}というおまじないのような記法が生まれることになり、あまりに多用されたため、多くのプログラマは、{0}が特殊なルールででもあるかのように錯覚しているのである。

ほとんどのプログラマは、言語を、厳密な規格の定義を読んで理解することなく、信じられないほど簡略化された、参考書によって、表面的に理解している。

たとえばdynamic_castは、polymorphicな基本クラスのポインターやリファレンスを、派生クラスのポインターやリファレンスに、実行時の型情報のチェック付きで変換するという機能を提供している。しかし、dynamic_castは、派生クラスのポインターやリファレンスから、基本クラスのポインターやリファレンスに変換するという機能も提供している。

struct Base { } ;
struct Derived : Base { } ;

int main()
{
    Derived d ;
    Base & b = dynamic_cast< Base & >( d ) ;
}

これは、static_castを使った場合と、全く同じである。実行時チェックも入らない。なぜならば、この型変換は、静的に行うことができるからだ。ある型が派生クラスであるということは、必ず基本クラスでもあるのだ。実行時チェックの入りようがない。しかし、多くのプログラマは、dynamic_castでは、必ず実行時チェックが入るものだと勘違いしている。何故このような機能があるのかというと、結局のところ、dynamic_castは、クラスヒエラルキーのナビゲーションのために使うものであり、一貫性をとるためではなかろうかと思われる。

同様にして、const_castで、CV-qualifierを付け加えることもできるし、まったく同じCV-qualifierにすることもできる。

int main()
{
    int const * ptr = nullptr ;
    const_cast< int const *>( ptr ) ;
}

これも結局、const_castというのは、CV-qualifierの増減に関する機能であり、CV-qualifierを減らすだけしか認めないのであれば、どうも一貫性にかけるのであろう。

ではなぜ、参考書の著者が、もっとも厳密に言語を教えないのかというと、結局、そこまで厳密に知っていても、通常のプログラミングには、あまり役に立たないためであろう。しかし、今日もネット上では、もし規格を数行読んでいたら、疑問の沸きようがない文法上の問題に対して、喧々諤々の論争が繰り広げられている。

追記:

さらに厳密に言うと、C言語とC++では、言語の定義の文面が異なる。対応する初期化リストの要素のないアグリゲートのメンバーは、C言語では、staticストレージと同じ方法で初期化される(つまりゼロ初期化される)。C++では、値初期化(value-initialize)される。値初期化では、アグリゲートとなりうる型はすべて、ゼロ初期化される。

もっとも、これらは単なる定義方法の違いというだけに過ぎない。

11 comments:

Anonymous said...

> かなり瞑想している。

迷走の typo ですね

江添亮 said...

どうも最近、Google日本語入力の変換結果が悪いのです。

Anonymous said...

各プログラミング言語のパラダイムに沿って、各言語の理想とするコーディングについて説明するのかと思ったら、
単なる方言やプログラマの人間的な慣習についての重箱の隅つつきに終始してた。
これなら、タイトルはC++はいかにすごいか、なんてのに変えておけば(表面的なことしか語っていないという意味で)せめて笑いはとれるだろうに。

Anonymous said...

C言語の未来は無いとか何を言っているのかと。
僕がC++を習っていた90年代後半の時も同じような事が言われていたけど、今でもC言語が一番使われている。

Anonymous said...

ハァ?こんなクソデザインのブログで何言っちゃってんの?アホか?もっとマシなデザインにしろよ。
私はいくら能力のある人でも、あなたのような横暴な人の意見は聞きません。
あなたの本も読まないことにします。

Anonymous said...

>次のC言語規格も、悲惨である。C言語に未来はない
そう言われ続けて何十年経つんだろう

文法の綺麗さとか所詮はマニア好みなネタにすぎないわけで、
20年後も30年後もC言語は第一線ですよ
残念ながらね

Egtra said...

はてぶのコメントで「初期化は明示したほうがよい」というようなことを言っている方々は、やはりint a[10] = {0};ではなくint a[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};と書くのが良いと考えているのでしょうか。聞いてみたいです。

私の想像では「いやいやそれはない。int a[10]; memchr(a, '\0', sizeof a);と書くんだ」という答えが返ってくるというのが一番多そうだと思っているのですが。

江添亮 said...

少なくともCでもC++でも、最初の要素が0で初期化され、残りの要素は0で初期化される、つまり、すべての要素が0で初期化されることが規格により保証されているので、リスト初期化を使うのは、0で明示的に初期化しているということになります。

Anonymous said...

プログラミングは面白い世界ですね~

Anonymous said...

解説ありがとうございます。配列に初期化について、まさにこんな情報を探していました。

> C言語に未来はない
パワーワードなので勘違いを呼ぶみたいですが、意図はなんとなく分かりますよ。
Cが今後何十年か一線で使用され続けるのはおそらく間違いないが、言語の未来を考えたときに、バグや可読性低下の温床なので、凄まじく方言の多すぎるCはやめて、ゆくゆくはC++に置き換えられるべきってニュアンスですよね。

Unknown said...

CppがCよりコンパクトで速ければね