C++の設計と進化(D&E) レバレッジメモ 1

どこにどの話題が書かれていたかすぐに探せるようにレバレッジメモを作ろうとしたがさすがに一気に作るのは無理だった…C++の話になる前に力尽きた。ふー、これでやっと1/4くらいかー。次回はC++の章からやる。



~第0章

ある人はD&Eを単なる歴史書と呼んだが…C++がなぜこのようにあるのかを理解することはプログラマがそれを使いこなすことを助ける。道具に対する深い理解は専門的な熟練工にはべからざるものである。

Simulaの最も初期の段階からコンテナは押し付けがましかった。Objectのようなクラスから派生していないと格納できなかった。intなどの基本型は入れられなかった。取り出すときにはObject型になってしまいキャストが必要だった。配列はユーザ定義型を格納できず、参照のみを格納できる。Smalltalk, Java, C#も同様の問題を抱えている。

STLのコンテナはとても不恰好に見えたが、コンテナが満たすべき性質のリストに1つを除いて合格した。

どんなコンテナも要素の列として捉えられる。コンテナはその最初の要素がどこにあるかと、最後の要素がどこにあるかを知っている。要素を指すオブジェクトのことをイテレータと呼ぶ。イテレータを使ってちょうどポインタの*のように指す値を取得することができ、++のように次の要素を指すようにできる。

STL以前、C++がサポートするパラダイムは次の3つだった「手続き的プログラミング」「データ抽象」「オブジェクト指向」、テンプレートはデータ抽象をサポートするものだと思っていた。STLでしばらく遊んでから4つ目を加えた「総称的プログラミング」

「テンプレートの分割コンパイルはとても困難だから実装者に負わせるべきでない」「テンプレートの分割コンパイルはデータ隠蔽原理に基づく正しいコードの組織化に必須である」

JavaC++に置き換わることはなかった。「2年以内にC++を殺す」と1996年になんども聞いた

Javaが「純粋なオブジェクト指向」というプログラミングの見方を広めたことは多くのC++プログラマに不必要なコードを書くように仕向けてしまった

新しい言語はいつだって単純さを売りにし、その後実世界のアプリケーション向けにサイズも複雑性も増す。C++Javaもこのシナリオから逃れられなかった --- Schemeもだね

プログラミング言語に関する従来の本はその言語の構造や使い方を説明している。しかし「なぜそんな構造なのか?」や「どういう経緯でそうなったのか?」という疑問をもつ人も多い。本書はこの2つの疑問に答える本だ。

1979年5月 C with Classes開発開始→10月最初の実装を使用開始
1982年 最初の外部発表
1983年 8月、C++実装開始→12月C++命名
1985年 最初の外部リリース
1989年 Cfront 2.0リリース

ALGOL68の最初の実用実装が世に出たのは1977

プログラミング言語とは何か、〜の目的は何か、
「機械に命令する道具」「プログラマがコミュニケーションするための道具」「アプリの高レベルの設計を表現する道具」「アルゴリズムを記述する道具」「実験のための道具」「装置を制御する方法」の全部。

言語の設計はユーザへの奉仕が第一である。ありとあらゆる分野から役に立ちそうなものを無節操に取り入れる。
成功する言語は一定の原理原則に基づいて設計されるのではなく現実の中で成長するものである。

第1章

Simulaでは複数のクラスがコルーチンとして動くので並行性を簡単に表現できる。またクラス階層という概念を使ってアプリケーションレベルの概念の同類関係〜変種関係を表現できる。ただし私はそれほど多用しなかった。並行性の方が重要。

Simulaのタイプシステムの表現力と、タイプエラーを補足するコンパイラの能力に感心。愚かなエラーや設計ミスを指摘してくれる。

Simulaで書いたプログラムは一つの大きなプログラムではなく小さなプログラムの集まりなのだ。だから書くことも、理解することも、デバッグすることも容易。

Simulaの実装系はスケールしなかった。クラスが多くなるとリンクに膨大な時間がかかる。あと実行時間の80%がGC。仕方が無いのでBCPLで書き直した。

大規模なプログラムを書くために適したツールは

  • プログラムの組織化能力(クラス、クラス階層、並行性、タイプチェック)
  • 高速なプログラムを作る能力。単一の言語に束縛されないために複数の言語で書かれたユニットを容易にリンクできる能力 --- !!
  • 実装系による性能差が大きくないこと、異色が容易であるためにはランタイムサポートが複雑すぎてはいけない

Cの構文はAlgol68の一般的な概念を特殊化したものと考えたほうがわかりやすい。理想の言語はAlgol68にSimulaのクラスを加えたものだ、と書いたこともある。しかし実用のためにはCの方がAlgol68よりずっといい。

第2章 C with Classes

並行性とSimulaふうのシミュレーション機能がC with Classes(以下CwC)の主な目的だった、しかし言語仕様に並行性に関する予約語を入れることはやめた。継承と特殊なメンバ変数の定義方法を組み合わせることで並行性をサポートするライブラリを作れるようにした。

C++は現在でも特殊なアプリのための特殊な機能 -- 複素数、文字列、行列… --などを言語自体で実装していない。ライブラリでサポートされるから。

CwCはプログラムの構造をより良くすることが設計の第一目的だった。ただし構造が改善されたことによってランタイムのパフォーマンス、コードサイズ、データサイズがCより悪化しないように気をつけた。

Cが元々もっている危険な醜い要素を取り去ることはいけないことだ。

当時も今も私はプログラムを書くための唯一正しい方法はありえないから、言語を設計するものもプログラマに一つのスタイルを強制してはならない、と信じている。

効果が実証されている多様なスタイルや技法を言語がサポートし、よくありがちな罠や落とし穴からプログラマを守る。

1980年にあった機能: クラス、派生クラス(w/o 仮想関数)、public/private、cstr/dstr、call/return関数(その後廃止)、friendクラス、タイプチェックと関数の引数の変換 1981年:インライン関数、デフォルト引数、代入演算子オーバーロード

Cの使えるマシンではCwCが使えた。Cの方言とみなされていた。クラスは抽象データタイプ機能と呼ばれていた(Stroustrup 1980 "Classes: An Abstract Data Type Facility for the C Language") オブジェクト指向という言葉が使われたのは仮想関数が導入された1983年

2.3 クラス

クラスはユーザが定義したデータタイプ(以下UDT)。クラスのメンバはそのクラスタイプの変数(つまりそのクラスのオブジェクト)の表現のされ方を定義する。さらにそのオブジェクトに対する操作(関数)を定義し、アクセス方法を指定する。メンバ関数は普通クラスの外で定義する

CwC, C++ではclassはtypeである。classがユーザ定義のtypeを意味するなら、なぜtypeと呼ばないのか?新しい用語を発明するのが嫌いだし、Simulaのclassのままで誰も困らないと判断したから。

Simulaのポインタと違って、CwCのポインタはUDTとbuilt-in typeの両方を指せる。

Simulaはクラスタイプのローカル変数やグローバル変数を扱えなかった。new演算子を使って空きメモリに置く。

CwCではインライン化できるのはメンバ関数のみ、インライン化の方法は関数の本体をクラス宣言の中に置く。多用するとゴタゴタするのでプログラマがインラインを乱用しなくなるという良い副作用。

従来からのC/FortranUNIX/DOS梨花を使って分割コンパイルができること
他の言語で書かれたプログラム部品のリンクが容易かつ効率的であること


CwCのオブジェクトは単なるCの構造体だった。CwCの最初の実装にはthisがなかった。すぐ加えた。ないと連結リストの操作などができない。SimulaのTHISのC++版。なぜリファレンスではなくポインタか?なぜselfではないのか?thisを導入したときにはまだリファレンスはなかったし、SmalltalkじゃなくてSimulaから主に用語を借りている。

Cで関数の宣言をf()と書くとどんな引数を何個でも取り型チェックをしない、という意味。CwCではf(void)で引数を取らない関数を宣言したが、ユーザはf()で引数を取らないようにしろと要求した。Cからの離反。

Cの宣言の問題点は"*"が前置なのに"[]"と"()"が後置であることだ。そこで後置のポインタ宣言"->"を提案した。 int v[10]->; ポインタの配列 int p->[10] 配列を指すポインタ。実装に失敗したので流産した。

私の意見としてはシンタクスに熱心になりすぎてタイプをおろそかにする人が多すぎる。C++の設計に置ける重要な問題は常にタイプと曖昧性とアクセス制御であり、シンタクスではない。言語のシンタクスは言語のセマンティクスに従うべきであり、それを抜きにして形だけを議論していると個人の好みを巡る議論に退化する。

C++のderived classはSimulaのprefixed classやSmalltalkのsubclassからの借り物。CwCには仮想関数がない。

仮想関数抜きの多態性: タイプフィールドを使って明示的にキャスト。マクロを使えばテンプレートなしにコンテナクラスを実現できる。最終的にはこういうテクニックがテンプレートへと成熟した。

CwCの最初のバージョンでも保護の仕組みはベースクラスとメンバの両方に適用された。ベースクラスのpublic/privateの議論は実装の継承かインターフェイスの継承かという議論より約5年古い:
[Snyder, 1986] "Encapsulation and inheritance in object-oriented programming languages"
[Liskov, 1987] "Data Abstraction and Hierarchy,"
実装だけを継承するならprivate継承、public継承ならインターフェイスも継承する。

new演算子をnewと呼んだのはSimulaに同名の演算子があったから。newはアロケーション関数を呼び出してメモリ領域を確保し、次にコンストラクタを呼び出してその領域を初期化する。この二つの仕事を合わせたものを今ではinstantiateとか、単にオブジェクトの生成という。つまり物理メモリの上にオブジェクトという論理的なものを作るのだ。

CwCにはメンバ関数が呼び出される前に呼ばれるcall()と帰る前に呼ばれるreturn()を持っていた --- !!AOPだ!
CLOSには同様に:beforeメソッドと:afterメソッドがあった。

CwCにはデフォルト引数があった。C++に一般的な関数オーバーロードが導入されるまでずっと存在した。