396 第 9 章例外処理 try { catch (OverfIow) { / / Overf10w や Overf10w から派生するものすべてを処理する catch (Matherr ) { / / overflow でない Matherr すべてを処理する こでは overflow は特別に処理され , 他のすべての Matherr 例外は , 一般の場合 で処理されることになる . もちろん , catch(Matherr) と発するプログラムは , 捕え た Matherr の種類を知らないであろう . それがなんであれ , そのコピーはハンドラに入 るときには Mather てになっている . 多くの場合 , これはまさに望んでいることである . そうでないときには , 例外をリファレンスで捕えることができる . 9.32 を見られたい . 例外を階層で構成することはコードの頑強さのために重要となり得る . 例えば , このよ うなグループ化のメカニズムなしに , 数学関数からの例外をすべて処理することを考えて みよう . これは , 例外を網羅的に並べることにより行わなければならないだろう : try { catch catch catch (Overf10w) { / ☆ (Underf10w) { / * ( zerodivide ) { / * これは長ったらしいだけでなく , はからずもいくつかの例外を並びから外しておくことに は仮定できない . 例えば , ライプラリの内部について心配しなければならないということ としても , システムのすべてのコードに喜んで変更を加える気になるということは一般に しあっても , そのソースコードがシステムのすべてのコードに使えることや , もし使える ればならない . 再コンバイルが必要なコードをすべて見つける方法はないことが多く , も 付け加わると , すべての math 例外を捕えようとするコードをすべて再コンバイルしなけ むことを , ほとんど保証するようなものである . 例えば , もし例外が math ライプラリに ことは , ライプラリの例外の集合に例外を付け加えるときにエラーがプログラムに忍び込 なりかねない . さらに , try プロック中にあるライプラリのためのすべての例外を並べる
594 第 13 章ライプラリの設計 両方の場合とも , 実行時の性能が損なわれ得るし , 生成されたコードは驚くほど大きくな り得る . 結果として , 人々は潜在的なエラーを無視し , プログラムがユーザの手に渡った 時にはそんなものは実祭には起こらないということを期待する気になるのである . このア プローチに伴う問題は , 徹底的なテストが難しく高価だということである . 結果的に , 太ったインタフェースは , 実行時の性能が重んじられる場合 , コードの正当 性について強い保証が必要な場合 , そして , 一般に良い代替策があるならどんな場合でも , 避けるのが最も良い . 太ったインタフェースの使用は概念とクラスの対応を弱め , 実装の ための単なる便宜として派生を使用することに対する歯止めを解くことにもなるのである . 13.7 アプリケーションの枠組み 上で述べられたクラスの種から造られたライプラリは , それらを結び付けるための組み 立て材料や方法を供することによってコードの設計と再利用をサポートする . アプリケー ションを組み立てる人は , これらの共通の組み立て材料がうまくはまり込むような枠組み を設計する . もうーっの , そして時にはより曖昧な , 設計や再利用へのアプローチは , ア プリケーションを組み立てる人がアプリケーションごとの特定のコードを組み立て材料と してはめ込んでゆくような , 共通の枠組みを確立してくれるコードを提供することである . このようなアプローチは , しばしばアプリケーションの枠組みと呼ばれる . このような枠 組みを確立するクラスはしばしば , 伝統的な意味での型であることはめったにないような 太ったインタフェースを持つ . それらは , 何もしないということを除けば , 完全なアプリ ケーションであれ , という理想を近似している . 特定の動作はアプリケーションプログラ マによって供される . 例としてフィルタ , すなわち入力ストリームを読み , ( もしかすると ) その入力に基づい たある動作を行い , ( もしかすると ) 出力ストリームを生み出し , ( もしかすると ) 最終結果 を生み出すプログラム , を考えてみよう . このようなプログラムのための素朴な枠組みは , アプリケーションプログラマが供するであろう演算の集合を提供するだろう : class filter { public : class Retry { public : virtual const char* message ( ) { return 0 ー
414 第 9 章例外処理 void f3(queue& q) i f ( q ・ empty ( ) ) else { message* m / / m を使う / / 待ち行列が空 q ・ get ( しかしながらテストと get 操作を分離するのは , 並行性が待ち行列にとって問題にならな い場合にのみ , うまくいくだろう . 。。例外処理はエラー処理である”という見方から軽々しく外れるべきではない . それを固 守している限り , コードは , 普通のコードとエラー処理コードという二つのカテゴリに明 確に分離される . これはコードをより理解しやすくする . 不幸なことに , 現実世界はそれ ほど明確には切り分けられず , プログラムの構成はそれを反映することだろう ( そしてそ うあるべきである ) . 例えば , もし待ち行列が正確に一度だけ空になるなら—get( ) 操 作がループで用いられ , 。。待ち行列が空 " がループの終わりを示す場合のように一 , 待ち 行列が空であることについての異常なもしくはエラーであるようなものは明らかに何もな く , そのため , 。。待ち行列の終わり " を示すのに例外を用いることはド例外処理はエラー 処理である " という見方を少しばかり拡張することになる . 他方 , このような待ち行列が 空のときに取られる動作は明らかに , ループの " 通常の場合 " に進行するものとは異なる . 例外処理は , if や for のような , より局所的な制御構造ほどは , 構造化されていな いメカニズムであり , 例外が実際に送出される場合には , しばしばはるかに非効率的であ る . それゆえ例外は , より伝統的な制御構造がエレガントでなかったり使用不能だったり する場合にのみ用いるべきである . 例えば , 待ち行列の例では , ゼロポインタの形で用い ることのできる , 完全に良い。。待ち行列が空 " の値があり , そのため例外を用いる必要は ない . しかしながら , 待ち行列がⅢ essage へのポインタの代わりに土 nt を返したとす ると , 。。待ち行列が空 " を表現するのに用いることのできる値はなかったであろう . その意 味で , 待ち行列 get ( ) は 1 の添字演算子と同値であり , 例外は。。待ち行列が空 " を表 現する方法として魅力的となるであろう . この議論は , 完全に一般的な待ち行列のテンプ レートは。・待ち行列が空 " を示すのに例外を用いる必要があり , 次のようなコードになる
410 第 9 章例外処理 ンドラを用いることを望むなら , 次のように書くことができる : set new handler ( &my_new handler ) : もし Memory_exhausted を捕えることも望むなら , 次のように書くかもしれない : void( *oldnh) ( ) set new handler ( &my_new handler try { catch (Memory exhausted ) { catch ( . set new handler ( oldnh 房 throw; / / 再送出する / / ハンドラをリセットする / / ハンドラをリセットする set new handler ( oldnh 房 又は , もっと上手に , 4 で記述された。、資源獲得は初期設定である " という技法を新し いハンドラに適用し , catch( . . ) ハンドラを避ける . 新しいハンドラでは , 工ラーがお助け関数に検出される場所からは , 余分な情報は渡さ 例外処理メカニズムは , 呼び出し手によって提供されるお助けルーチンを呼び出す関数呼 も要するからである . 一般に , ソフトウェアの別々の部分は , 別々のままにしておきたい . 考えである , なぜなら , 一方のコードへの変更が , 他方の理解や , ことによると変更さえ ということが認識されるべきである . この依存性を最小化するというのは , 一般には良い 情報が多くなればなるほど , ますますその二つのコードはお互いに依存するようになる , 実行時工ラーを検出するコードとそのエラーを訂正するのを助ける関数の間で渡される と呼ばれる . て用いられる関数や関数オプジェクトへのポインタは , しばしばコールバック (callback) ジェクトはこの例である . サービスを要求したコードへ呼び。。戻る " ためにサーバによっ クトに置くことができる . い 0.4.2 のマニピュレータを実装するのに用いられる関数オプ ラスの一部にして , 助けを必要とする関数は , 必要とされる情報をそのクラスのオプジェ れない . もしさらに情報を渡すことが必要なら , ユーザーが供給するお助け関数をあるク
1.2 プログラミングパラダイム 27 " 型フィールド "k は , 演算子 draw( ) や rotate( ) が処理している形状の種類を決定 する際に必要である ( pascal 風の言語ではタグ k の可変レコードを使っている ) . 関数 draw( ) は , 次に示すように定義できる : void shape : : draw( ) switch (k) { case circle: / / 円を描く break; case triangle: / / 三角形を描く break; case square: / / 正方形を描く break; これは混乱を招く . draw( ) のような関数は , あり得るすべての形状を知っていなければ ならない . したがって , そのような関数はいすれも新しい形状がシステムに現れるたびに コードが増えてゆく . 新しい形状を定義するときには , 形状に対するすべての演算を調べ て ( 多分 ) 変更しなければならない . すべての演算についてのソースコードにアクセスし なければ , 新しい形状をシステムに追加することはできない . 新しい形状を追加すること は形状の重要な演算すべてのコードを。。触る " ことを含むので , それには優れた技能を必 要とし , また他の ( 古い ) 形状を扱うコードにバグを引き起こす可能陸をもたらす . 特定 の形状の表現を選択すると , 一般的な型 shape の定義により示される典型的な固定サイ ズの枠組みに適合するようにそれらの表現 ( の少なくともいくつか ) を選択しなければな らない , という要求により , 厳しく束縛されかねない . 1.2.5 オプジェクト指向プログラミング 問題は , 形状の一般的な性質 ( 形状には色がある , 描画できる , 等々 ) と , 特定の形状 の性質 ( 円は半径を持っ形状である , 円の描画関数で描かれる , 等々 ) の間に区別がない ということである . この区別を表現しそれを利用することが , オプジェクト指向プログラ ミングの定義である . この区別を表現し利用することを許すような構造物を持っ言語は ,
第 13 章ライプラリの設計 ので , 同様な演算に異なる具体型を用いているコードは置き換えが利くように用いること 566 関数のユーザは , その関数が期待するまさにその型を用いなければならない . 例えば : ができない , ということである . 普通 , 同様なコードをくくり出すのは不可能である . vector ▽ ( 10 0 房 slist sl; void user( ) my ( s 1 your ( v ) : my ( v 房 your(sl); 、工ラー 、エーラー . 型の不一致 型の不 一致 それを補償するために , サービスの提供者はユーザに選択を与えるためにいくっかのバー ジョンの関数を供給することがあり得る : ▽ 0 土 d void void void void my(slist&); my (vector& your(slist& 房 your( vector& ) : user( ) vector v ( 10 0 slist sl; my ( s 1 your( v my(v); your(sl); / / 今は ok : / / 今は ok : my(vector& ) を呼び出す your(slist&) を呼び出す 関数のコードはその引数の型に決定的に依存しているために , 叫 ( ) と your( ) の各バー ジョンは別々に書く必要がある . これは害になり得る .
392 第 9 章例外処理 try { catch (xxii) { try { / / 何か複雑なこと catch (xxii) { / / 複雑なハンドラコードが失敗した 9.3 例外の名前付け 合 , 貧しいスタイルを示すものである . しかしながらこのような入れ子は人間の書くコードではめったに有用ではなく , 多くの場 囲ェラーの原因となったインデックスを知りたいと思ったなら : そのオプジェクトにデータを乗せることによって , それを行うことができる . 例えば , 範 ではなくオプジェクトである . 送出点からハンドラに余分な情報を送る必要があるなら , 例外はその型を記述することによって捕えられる . しかしながら , 送出されるものは型 class Vector { public: class Range { public : int index ー Range ( int 土 ) index( 土 ) int& operator [ ] ( int i )
5.3 name* nn new name ー nn—>string = new char[strlen(p) 十 1]; strcpy(nn—>string'p); インタフェースと実装 203 nn—>value nn—>next tb1 return nn; tb1 ー さてここで , table クラスを改良して , 卓上計算機の例で使ったような , ノ、ツシュされ た検索を行うことを考えてみよう . いま定義したバージョンの table クラスを用いて書 かれたコードがそのまま変更なしに正当なものとなるべきである , という制約があるので , それを行うのはより難しくなる : class table { name** tb1 ー int Size; public : table(int sz -table( 15 name* 100k ( char* ′ int name* insert(char* S) = 0 房 { return 100k ( s ′ 1 ) ハッシングを使うときに , 表のサイズを特定する必要があることを反映して , データ構 造とコンストラクタを変更した . コンストラクタにデフォルト引数を提供することにより , 表サイズを指定しない古いコードがそのまま正しくなることを保証している . デフォルト 引数は , 古いコードに影響を与えずにクラスを変更しなければならない状況では有用であ る . コンストラクタとデストラクタは , , こではハッシュ表の作成と削除を行う : table: :table(int sz) if ()z く 0 ) error("negative table s 土 ze " new name* [Size tb1 sz 0 ー i<s 2 : 土十十 ) tb1 [ i ] fO て (int 土
522 第 12 章設計と C + + 引数についての k ( ) の仮定を明示したことにより , 工ラー検出が実行時からコンパイル 時に移ったことに注意されたい . 上のような精巧な例は , C + + において , 異なる型モデル についての経験に基づいて設計を実装しようとした結果である . それは普通可能であるが , 。不自然な " 見かけと典型的な非効率的なコードにつながるのである . 設計技法と使用さ れるプログラミング言語とのこのような不適合は , 自然言語の間の単語の置き換えによる 翻訳の結果と比較することができるだろう . 例えば , ドイツ語文法による英語は英文法に よるドイツ語と同じぐらいぎこちないし , 両方ともそれらの言語の一方だけが流暢な誰か にとってはほとんど理解できないこともあり得る . この例は , プログラムにおけるクラスは設計の概念の具体的な表現であって , そのため クラスの間の関係を曖昧にすることはその設計の基本的な概念を曖昧にすることになる , ということを反映している . 12.1.4 混成設計 物事を行う新しい方法を組織に導入することには苦痛を伴うこともあり得る . 組織にお ける組織と個人との分裂は重大なことになり得る . 特に , 生産的で熟達した、。古い学派 " のメンバが , 一夜にして、。新しい学派 " の効率的でない初心者になる , といった突然の変 化は普通受け入れられない . しかしながら , 変化なしで多数の利益を達成することはまれ であり , 意味のある変化は普通危険を含むのである . C + + は , 技法の漸進的な採用を可能にすることによりこのような危険を最小にするよう に設計された . C + + を用いることから得られる最大の利益は , データ抽象 , オプジェクト 指向プログラミング , オプジェクト指向設計を通じて達成されることは明らかであるにも かかわらず , それらの利益を達成する最も迅速な方法とは過去との交わりを徹底的に絶っ ことである , ということは明確になっていない . 場合によってはこのような鮮やかな断絶 も可能ではある . それよりもよく見られることだが , 改良への要望は , どのように移行を 管理するかについての懸念によって抑えられるのである一もしくはそうなるはずである . 次のことを考えてみよう : ー設計者とプログラマは , 新しい技能を獲得するための時間を必要とする . ー新しいコードは , 古いコードと協調する必要がある . ー古いコードは , ( しばしば無期限に ) 保守する必要がある . ー設計とプログラムについての , すでにある仕事は , ( 間に合うように ) 完了する必 要がある . ー新しい技法をサポートするツールは , 身近な環境に導入される必要がある .
578 第 13 章ライプラリの設計 これは良いし一般的なのだが , もし , 渡される多くの set が slist によって実装されて いることを知っており , 一般の集合よりもリストについての方が有意により効率的である ループのアルゴリズムを知っており , そして , ( 討韻リから ) このループが私のシステムのポ トルネックになっていることを知ったとしたら , どうであろうか . そのとき私のコードを slist を別に処理するように拡張することは , 私にとって行う価値があるだろう . set 引数の実際の型を決定することが可能であると仮定して , 私は次のように書くことができ よう : void my(set& s) if (ref_type info(s) static_type_info(slist set) ) { / / s は s 1 is t である / / 実行時型表現を比較する slist& s1 for ()* p else { for ()* p (slist&)s; s 1 . first ( p ー p = s1. next ( ) ) / / パワーアップされたリストアルゴリズム s . f irst ( p ー P s ・ next ( ) ) / / 普通の集合アルゴリズム てみよう . 例えば , 我々はい 3.4 で定義されたような dialog box を持っており , それ クラスとそれから派生したすべてのクラスのための特別なコードを提供したい場合を考え 別なコードを実際に望んでいるので , このバージョンはうまくいく . しかしながら , ある slist が具体クラスであり , 我々は引数が slist set そのものであるときにのみ特 る演算についてはインライン化も使えることにラ意されたい . 一度具体型 sl 土 st が用いられたら , 特別なリスト演算が使用できるだけでなく , 鍵とな