オプジェクト - みる会図書館


検索対象: 月刊 C MAGAZINE 1992年6月号
26件見つかりました。

1. 月刊 C MAGAZINE 1992年6月号

の関数を登録します。 exit ( ) は main ( ) が正常に終了するときに も , コールされます。 exit ( ) がコールされ ない唯一の場合は , ライプラリ関数 ab 。 rt ( ) をコールしてプログラムを終えたときて、す (abort( ) は static なオプジェクトのデストラ クタコールをスキップするのて、 , C 十十て、は 勧められない乱暴な方法て、す ) 。 ioserror の実装 以上のような情報がすべて , IOSERROR. CPP (List 6 ) に盛り込まれていることが , お 分かりと思います。このファイルて、真先に 目につくことは , これが ioserror を指すポイ ンタを収める , eh list という名のコンテナ クラスて、あることてす oehandler list という 名の , このクラスのたったーっの static なイ ンスタンス ( このファイル内て、のみ可視 ) を , , こに ioserror クラスのオプジェクト 作り 〔の this 〕をすべて , その生成時に登録しま す。この登録はこ覧のように , ioserror のコ ンストラクタの中て、行なわれます。この eh list: :Register() という関数コールは , オプジェクトのポインタ (this) を , 配列に収 めるだけて、す ( 配列の境界オーバのチェック も行なう ) 。この実装は単純て、すが , より一 般的には , リンクトリストを使ってもよい て、しよう ( そうしてもインタフェイスは変わ りません ) 。 eh list のコンストラクタは atexit ( ) をコ ールして , 〔 exit ( ) 時にコールされる関数と : dump ( ) 関数を登録しま して〕 eh list: す。すると , プログラムの終了時にはすべ ての ioserror オプジェクトがダンプされま . dump ( ) のようなメ す。しかし , eh list: ンバ関数を , atexit() のような普通の関数 の引数にすることは , C 十十て、は普通は不可 能て、す。 C 十十て、は , メンバ関数のコール と , それが属するオプジェクトとが , 密接 に結びついているからて、す。て、はなぜ , こて、はそれがて、きているのて、しよう ? eh list : : Register( ) と eh list : : dum p ( ) は , static なメンバ関数として宣言され ています。 static なデータメンバと同様に static なメンバ関数はクラス全体に対して働 き , static て、ないデータメンバにはアクセス て、きません〔 static て、ないデータメンバ = 個々 のオプジェクトごとに作られるデータメン バには , アクセスする必要がないのだか ら〕。普通のメンバ関数のように , オプジェ クトのアドレスを隠れ引数〔コンパイラが メンバ関数に対して暗黙裡に渡している引 数〕として渡す必要もありません。 むしろ static なメンバ関数は , 普通のグロ ーバルな関数と似ていて , ただ名前がクラ ス内部に隠される , という点だけが違いま す。 static なメンバ関数は , 普通の関数と同 じようにコールて、きますが , そのとき eh li : eh list ( ) の例のように , スコープ指 st : 定演算子を使ってクラス名を指定すること 2 : 5 : 9 : 11 : 12 : 13 : 14 : 16 : 17 : 18 : 19 : 20 : 22 : 23 : 24 : 25 : 26 : 27 : 28 : 29 : 30 : 31 : 32 : 33 : 34 : 35 : 36 : 37 : 39 : 41 : 42 : 43 : 45 : 48 : 49 : 50 : 52 : 57 : 58 : 59 : } List 6 IOSERROR ℃ PP iostr m を使うエラーハンドラの実装 1 : / / : IOSERROR. CPP ー #include ” ioserror. h ” 3 : #include く stdlib. h> 4 : #pragma warn -inl / / "not inline ”というメッセージを無視する 6 : / / もっと本格的な実装では , 固定サイズの配列は使うべきではないだろう . 7 : class eh—l ist { / / ioserror 用のコンテナクラス 8 : / * このコメントアウトした部分はテ・イスクで提供されたオリシ・ナルハ・一シ・ヨン .. コンハ。イラが ' eh list: :size' is not accessible というエラーメットシ・を出し , iostest の in ( ) の内容を実行後 , 暴走に突入する . これはコンハ。イルはできるが , 出来上がった . EXE は , 15 : / * 以下は COMPUTER LANGUAGE 92 / 2 月号に載っているハ・一シ・ヨン .. static int cursor; StatiC ioserror* 1 istcsize] ; enum { S i ze = 100 } ; 〃 * * * ェラーハント・ラの存在許容最大数 コンハ。イルできない . ( たぶん , ' list ' が不完全宣言なので sizeof ( eh ー list ) が不正になるから ? ? ) 事 / static ioserror* listC]; / / * * * ' size' はここでは参照できない * static int cursor; 21 : public: enum { S i ze = 100 } ; 〃 * * * ' size' を public 部に移す eh-l ist() { 〃 exit ( ) に際して du 叩 ( ) を実行する atexit(eh—list: :dump); / / ェラーハント・ラが新たに作られるたびに登録する static VOid Register(ioserror* (h) { if(cursor く size) 1 istCcursor + + ] ニ eh; else clog くく "increase eh—list size" くく nl; / / すべてのエラーハンドラの内容をダンプする static void dump() { whi le(cursor-- > の 1 ist[cursor]- 〉 du 叩 () ; 40 : / / static なデータメンパの定義 ( 0 に初期化する ) ioserror* eh list::list[eh list::size] = { 0 } ; 〃 * * * ェラーの出る行 int eh list: :cursor 44 : static eh_list ehandler list; 〃このファイルの中でのみ可視 55 : / / したがっていかなるエラー情報も失われない . 54 : / / 最初にすべてのエラーハンドラのデータをダンプする . 53 : / / プログラムを終了させるエラーハンドラが eh_list::Register(this); / / static なメンバ関数をコール : ostrstream(msg, bsize), classname(class—name) { ioserror: : ioserror(char * class_name) 47 : / / オブジェクト (this) を eh_list に登録する : 46 : / / ostrstream を , msg バッフアに出力するように初期化し , return OS ; / / コンハ。イラに文句を言われないためのタ・ミーの return 文 exit(l); 56 : ostream& terminate(ostream& (s) { / / これはマニピュレータ C + + でエラー表示を楽にやろう 19

2. 月刊 C MAGAZINE 1992年6月号

告を出す (char 型が signedchar 型として扱わ れる CPU て、は , この使い方がバグの原因と なるため ) 。 —Wconversion プロトタイプ宣言によって , プロトタイ プ宣言がなかった場合とは異なる型変換が 行われるケースを検出する。たとえば float の引数を取る関数のプロトタイプ宣一 のような関数は , うつかり宣言を忘れて呼 び出すと正しく動作しないのて、 , 危険な場 合がある。 これらのオプションの中には , 通常使わ れるような正しいコードに対して誤って警 告を発し得るような「厳しすぎる」オプショ ンも含まれています。そのため , 単に -Wa 11 とした場合には , これらの厳しすぎるオプ ションを除外して , 常に妥当だと思われる ような警告のみがすべて出力されます。 a. out 以外のオプジェクト 形式のサポート 今まて、は , GCC て、正式にサポートされて いたのは , BSD 系 UNIX の a. out 形式のみて、 した。しかし UNIX て、は , システムによって これ以外にも多くのオプジェクト形式が存 在しています。そのため , 以前のバージョ ンて、は , GCC を COFF( これも多くの UNIX システムて、使われているオプジェクト形式 て、す ) に対応させるための差分ファイルが , オリジナルの配布ファイルとは別に配布さ れていました。 UNIX 以外の OS(VAX の V MS など ) のオプジェクト形式用の GCC も同 様の方法て、対応されていました。 それに対して Ver. 2 て、は , ・ a. out ・ COFF ・ ECOFF ・ ELF ・ XCOFF ・ VAX-VMS 形式 ・ OSF-Rose という形式がすべて標準の配布ファイルて、 サポートされています。 クロスコンノヾイラの サポート USENET 上て、は , GCC についての質問お よびそれへの回答を行う gnu. gcc. help という ニュースグループがありますが (help-gcc と いう名前のメーリングリストがこれと同じ 内容て、す ) , こには , 「 GCC をクロスコン パイラとして使いたいのだがどうすればよ いか」という質問が頻繁に現れます。 クロスコンパイラとは , オプジェクトコ ードを実際に動作させるマシン ( ターゲット マシン ) とは別のマシン ( ホストマシン ) て、コ ンパイルを行い , その結果をターゲットマ シンにダウンロードして , 実行のみをター ゲット上て、行うという仕組みて、す。コンパ イラが載せられないマシンのためのコード を開発するには , このような方式をとる必 要があります。 GCC はソースが入手て、き , さまざまな CP U 用の定義ファイルが付属しているほか , 新 たに開発した CPU 用のコードを出力させる ことも比較的容易なのて、 , クロスコンパイ ラとしては理想的て、す。上記のような質問 が出るのも当然といえるて、しよう。 しかし , Ver. 1 て、は , 通常のインストー ル手順だけて、はクロスコンパイラとしての インストールがて、きず , ューザが設定を自 分て、書き換えて対応する必要がありました。 それに対して Ver. 2 て、は , たとえば次の ような方法て、 , クロスコンパイラとして GC C をインストールするような設定が簡単にて、 きるようになりました (List 1 ) 。 これは , SunOS 4.1 が動いている SPAR C マシン上て、 , SunOS 3. x が動いている Su n ー 3 用のコードを生成するクロスコンパイラ をインストールする , という意味て、す。 のような設定を行うことによって , Sun-3 用 のコードが Sun-3 ( CPU が 680X0 なのて、一般 に SPARC よりも低速て、す ) て、はなく SPARC マシン上て、高速にコンパイルて、きます。 また , ーー target 指定によって異なるターゲ ット環境を設定してコンパイルした複数の GCC をインストールして , それを単一の gc c コマンドから切り換えて使うこともて、きる ようになっています。その切り換えのため に使うのが gcc コマンドの一 b オプションて、 , -b sun3-sunos3 のように , ターゲット環境 名を引数として指定します。 通常 , -b オプションを指定しないと , ホ ストマシンと同じ環境がターゲット環境と して使われます。 これと似た機能として , 複数のバージョ ンの GCC をインストールしておき , -V 2.1 のようなオプションを gcc コマンドに指定す ることによって , 指定したバーションのコ ンパイラを起動する , ということもて、きる ようになりました。これによって , ・安定して動作するやや新しいバージョン ・性能がいいが不安定な最新バージョン の GCC がある場合 ( 現状がそうて、すが ) に デフォルトて、は前者が使われるようにして , 明示的に -V オプションをつけたときにだけ 最新バージョンが使われるようにすること がて、きます。さらに古いバーションもテス ト用にインストールしておくのもいいかも しれません。 位置独立なコードの生成 Ver. 2 て、は , List configure -host— —sparc¯sun—sunos4.1 位置独立コードが多くのタ ¯target:=sun3—sunos3 速報 GUN C コンバイラ Ve 「 . 2 25

3. 月刊 C MAGAZINE 1992年6月号

0 として表現されます。本稿て、は , ・コンポーネント ( 構成要素 ) ・インタフェイス と呼ぶことにします。 Fig. 1 オプジェクトレンズコーティング規約 ・概要 オプジェクトレンズ ( ObjectLens ) は , C 言語を使って , オプジェクト指向風のコーティングをする 手法である。マクロ定義ファイル object. h と基本関数ファイル object. c が提供される。 ・オプジェクト オプジェクトは構造体とそれを扱う関数のセットて表現する。オプジェクトの宣言はそのオプジェ クト名がついたヘッタファイルで行う。ヘッダファイルは 2 回 include されても大丈夫なように # ifndef オプジェクト名 H … #endifC くくる。その内部 Cobject. h を include する。構造体を使用しない単 純なオプジェクトの場合には typedef を使用する。「 ank. h を参照のこと。 ■オプジェクトのコンポーネント定義 オプジェクトのコンポーネントは , Component(OBJECT) という行以降で , 構造体のメンノヾを列挙して定義する。 OBJECT はオプジェクト名 , ただしすべて 大文字て書くこと。 ■オプジェクトのインタフェイス定義 オプジェクトのインタフェイスは , lnterface (Object) という行以降で , インタフェイス関数のプロトタイプ宣言を列挙することで定義する。 Object はオ プジェクト名 , 初めの 1 文字のみ大文字で書くこと。プログラム中で使うオプジェクト名はこちらで ある。 ■オプジェクトの生成・消滅 オプジェクトのインタフェイス関数として , Object NewObject(... ) : Nothing DeleteObject(Object) : のふたつは必須である。 Object には個々のオプジェクト名が入る。 NewObject( … ) はオプジェクト を生成し , DeleteObject( ) はオプジェクトを消滅させる。 NewObject(... ) の ... の部分はどんな引 数をとってもよい。この引数の型によってたとえば Object NewObjectByName(Name) : のように By 何々という名前を付加してもよい。その場合でも NewObject(...) は用意すること。 一所有権の移動 New および Get で始まるインタフェイス関数の返り値として得られたオプジェクトは , 使用後 , 返 り値を得た側て DeIeteObject する必要がある。逆に G ⅳ e ではじまるインタフェイス関数に引数で与 えられたオプジェクトは , 使用後 , インタフェイス関数の側で DeIeteObject する必要がある。 付録ティスクの deck. h/c や hand. h/c を参照のこと。 ーインタフェイス関数の規約 〇インタフェイス関数の第一引数は必すそのオプジェクトへのポインタになる。このポインタは通 常 this という変数名で , オプジェクト自分自身を指す。ただし , New で始まる関数にはこの規 約はあてはまらない。 インタフェイス関数名はオプジェクト名を含む。たとえばオプジェクト ca 「 d のインタフェイス関 数は何々 Card という名前になる。 コンボーネントの参照関数はコンポーネント名を含んだ名前にする。たとえばカードのランクを 参照する関数なら , RankOfCa 「 d という名前になる。 ドなら真を返す関 数は , SameCa 「 d という名前になる。 ■組み込みオプジェクト 次のオプジェクトははしめから組み込まれている。 「無」オプジェクト ( 型 ) Nothing 「無」オプジェクト ( 実体 ) 真偽値オプジェクト ( 型 ) Boolean 整数オプジェクト ( 型 ) lnteger たくなってきます。そうすれば , 名前にい ちいち card をつけなくても , 構造体 CARD に関連する関数だということがわかっても らえるからて、す。 しかし , 残念ながら C て、は構造体のメンバ として関数を含めることはて、きません。ど うしても含めたいときには関数へのポイン タをメンバとして含めることになります。 これだとポインタの領域のメモリが構造体 ひとつひとつごとに消費されてあまりうれ しくありません。 一方 , C 十十て、は構造体のメンバに関数を 含めることがて、きます。もちろん構造体の 大きさがそのために大きくなることはあり ません。そこに書かれたメンバ関数は名前 が適宜変形され , コンパイラによって正し く処理される形になってくれるのて、す。 こて、は詳しく述べませんが , C 十十コンパイ ラが自動的に名前に card を付加するような ものて、あると考えればよいて、しよう。 変数と関数の両方をひとつに集めるのは , 想像以上に大きな意味を持っています。デ ータを保持する変数と , どのような操作を 行うかを示す関数をひとつに集めると , 単 に複雑なデータが表現て、きるだけて、はなく , その型がプログラムの中て、どういう役割り を果たすものなのかがはっきりしてくるの て、す。 「変数と関数」は「データとインタフェイス」 あるいは「コンポーネントとインタフェイス」 と呼ぶことがて、きるて、しよう。 SmaIItaIk な ら「インスタンス変数とメッセージ」と呼ぶ ところて、す。いすれにせよそのモノが , ・何でできているか ・どう使うのか をはっきりさせることはとてもたいせって、 す。このふたつがはっきりすれば , そのモ ノは「オプジェクト」と呼ぶにふさわしいも のとなってきます。 C 十十て、は上記の 2 点は それぞれ , ・メンバ変数 ・メンバ関数 ちょっと注意 こて、はオプジェクトの 基本的な概念のみを扱っています。継承な どの概念については触れません。また機会 があったら紹介することにします。 プログラミングの工ッセンス 55

4. 月刊 C MAGAZINE 1992年6月号

うにはまず登場人物は何かを考えます。登 ちょっと考え方を変えましよう。トランプ フのプログラム 場人物・・・・・・つまり関連するオプジェクトを のカードの本質は何て、しよう ? 紙て、て、き ンプのオプジェクト化 設計するところから始まるのて、す。問題 1 て、 ていることは本質的て、はありません。プラ 今月のプログラムは問題 1 を大幅に拡張 はカード君だけを考えましたが , 今度はそ スチックて、もいいし , 木て、も石て、もいい し , 「トランプのゲーム」をオプジェクト指 の親類縁者も含めて設計します。 ってことはカードの材質は問題じゃないっ 向風に記述したものて、す。オプジェクト指 トランプゲームに登場するオプジェクト てとて、す。 向「風」なのは , 今回のプログラムは C 十十て、 は何て、しよう。 とにかく登場しそうなモノ トランプのカードの本質は何て、しよう ? はなくて C て、書いたためて、す。 C て℃十十の を全部あげてみましよう。足りなければま 本質とは何ぞや ? これがなかったらもう まねごとをやってみました。 こて、はちょ た追加すればいいのて、す。 トランプのカードとはいえない性質のこと っと特殊な C のコーディングを行います。 うーん。まずトランプのカードは必須て、 て、す。問題 1 て、考えましたからもうわかりま のコーディング方法に便宜上 , オプジェク しよう。それからゲームを行うプレイヤー すね。 ート , ダイヤ , クラブ , スペード , トレンズ (Object Lens) と名前をつけまし かな。プレイヤーは配られたカードを持っ というスートと , A, 2 , 3 , ・・・ J , Q, K と た。オプジェクトレンズの詳細は Fig. 1 をご から , それをひとつのオプジェクトにして いうランクの組み合わせがトランプのカー 覧ください。オプジェクトレンズは今回の もいいだろう。 ドの本質て、す。 それはいわば手というオプジェクトだな。 テーマ「コンポーネントとインタフェイスを 一枚のカードは必ずひとつのスートとひ 集める」ことを主眼にコーディングをします。 そしたら , プレイヤーに配って残ったカー とつのランクて、て、きています。たとえばス オプジェクトレンズて、プログラムを書く ドを積んて、おく山もひとつのオプジ土クト ペードの A なら , スペードというスートと A には , List 2 をインクルードしてそこに書か になるかもしれない。よし , まずはこのく というランクを持っています。スートやラ れているマクロ Component, lnterface を使 らいか。 ンクも後ほどオプジェクト化することにし カードから順に詳細な検討を行ってみま 用します。マクロ Component 以降にはその て , とりあえずこれて、いいて、しよう。カー オプジェクトのコンポーネントを , マクロ しよう。検討のポイントは , 先ほども述べ ドはスートとランクというふたつのオプジ たようにふたつあります。そのオプジェク lnterface 以降にはそのオプジェクトのイン ェクトて、作ることにしました。 タフェイスを記述します。 Component 以降 トが , カドはどう使うか にメンバ変数を , lnterface 以降にメンバ関 何でできているか ( コンポーネント ) 数を書くようなつもりて、書きます。詳しく どう使うか ( インタフェイス ) カードはどう使うのて、しようか。今度は はソースプログラムを見てください この 2 点をチェックするわけて、す。 オプジェクトレンズは C 十十のクラス定義 カードのインタフェイスを考えます。 の機能を C て、真似しようというものて、す。 カドは何でできているか カードは見ることがて、きます。見て , れはスペードだとか工ースだとか判断する ういうコーディングが直接役に立つかはわ カードは何て、て、きているのて、しようか。 かりませんが , ひとつの実験として参考に ことがて、きます。つまりそのカードのスー カードのコンポーネントを考えます。トラ トが何て、あるか , ランクが何て、あるかを知 してください ンプのカードは紙て、て、きています。確かに るインタフェイスが必要になるわけて、す。 トンプの登場人物 現実のカードはそうて、す。しかし今やろう スートやランクの値を変更するというイ としているのはトランプのカードをプログ ンタフェイスは不要て、す。だって , 普通ト ラム上のオプジェクトにすることて、すから , : クト指向てプログラミングを行 ランプのゲーム中にカードを書き換えたり しませんから。あとは , すべてのオプジェ object. h C 言語でオプジェクト指向風なプログラムを書くためのヘッタ ( 一部 ) クトに共通の , カードを作成するインタフ ェイスとカードを削除するインタフェイス が必要て、す。まあ必須なインタフェイスは このくらいかな。 先ほどはカードをシャッフルするという インタフェイスも考えましたが , 今度はそ List 1 : #define Component(sname) 2 : #define lnterface(class) 3 : #define New0bject(sname) 5 : #define N0thing void 6 : typedef int Boolean; 7 : typedef int lnteger; typedef struct sname { } *class; (struct sname *)NewBySize(s izeof(struct sname)) 56 C MAGAZINE 1992 6

5. 月刊 C MAGAZINE 1992年6月号

とえば , GetCardOfDeck ( ) て、は得られるカ ードの所有権は Deck を離れますが , Get の つかない CardOfDeck( ) て、は所有権は離れ ません。 DeleteCard ( ) の責任はまだオプジ ェクト Deck 側にあるのて、す。 C 十十て、は AUTO 変数にとられたオプジェ クトは消滅時に自動的に DeIete されるのてす が , オプジェクトレンズは C の機構しか使え ないのて、 , プログラマが自分の責任て、 DeIe te の管理をしなくてはなりません。関数名に Give や Get がついたら所有権が移動するとい うのは , Delete の管理をしやすくするための オプジェクトレンズて、の約束ごとて、す。 プイヤーは何でできているか 次はプレイヤーて、す。プレイヤーは何て、 て、きているて、しようか。プレイヤーの本質 は何て、しようか。考えてみましよう。 まあ , 名前は必要て、しよう。名前もひと Fig. 2 オプジェクト Ca 「 d を作る 実世界 つのオプジェクトて、す。プレイヤーは配ら れたカードの集合 ( 手 ) をひとつ持っていま す。 プレイヤーの年齢は ? 性別は ? それ らは , 入れたかったら入れてもかまいませ ん。筆者はそれらのデータをプレイヤーの トラ コンポーネントに入れませんて、した。 ンプ占いなどをオプジェクトて、表現するな ら年齢・性別も重要な要素になるかもしれ ませんね。 そんなときには年齢・性別もプレイヤー のコンポーネントに入れるといいて、しよう。 オプジェクトのコンポーネントを何にする かは , とくに決められているわけて、はあり ません。すて、に決まっている結果をなぞる のて、はなく , プログラムを書く人自身が「こ うしよう」と決めてやるのて、す。どういうオ プジェクトになるかは設計者しだい うわけて、す。そこにこそプログラミングの 実際 , 今回このトランプゲームをオプジ ェクトレンズて、記述しているとき , 妙に心 が騒いだものて、す。「カードの山をカードと は別のオプジェクトにしたほうがいい」とか 「おお , プレイヤーというオプジェクトを作 ればいいんだ」とか , 実世界をコンピュータ 上に移す作業は胸がドキドキするような体 験て、した。 とくにオプジェクトレンズ自身 をデザインしながらのトランプ作りはなか なか楽しいものて、した。 プレイヤーのインタフェイスを考えます。 プレイヤーの名前を取得するインタフェイ スは必要て、すね。ほかの人に名前がわから なくなってしまいますから。問題は , プレ イヤーが今持っている「手」を得るインタフ ェイスて、す。 プレイヤーの「手」はそのプレイヤーが今 プイヤーはどう使うか 面白さがあるのて、す。 コンピュータの世界 Component (CARD) Suit suit; Rank rank; lnterface(Card) SuitOfCard ( ) ; IsSameCard( ) ; RankOfCard( ) ; Fig. 3 オプジェクト Deck を作る 何でてきて いる ? 実世界 カードの 「山」 何でできて いる ? ( コンポーネント ) どう使う ? ( インタフェイス ) ランクと スート ランクを見る スートを見る 調べる 等しいか カードが ( コンボーネント ) シャッフルする ( インタフェイス ) どう使う ? カードをとる コンピュータの世界 Component (DECK) Card card;[53], lnteger top, lnterface (DECK) ShuffIeDeck( ) ; GetCardOf Deck( ) ; 58 C MAGAZINE 1992 6

6. 月刊 C MAGAZINE 1992年6月号

督懾 9 kSSemlCe 0 どんなカードを持っているかを表わします。 だから本来「手」はプレイヤー以外に見せる べきて、はありません。したがって , 「手」を 得るインタフェイスは作らない しようか。しかし今回はゲームの途中て、ユ ーザに「いまあなたが持っているカードはこ れこれて、す。どのカードを出しますか ? 」と いう間いを発行するつもりて、すのて、 , 「手」 そほかのオプジェクトたち なりました (List 7 ) 。 以上から , プレイヤーもオプジェクトに プイヤーのオプジェクト化 します。 を得るインタフェイスは残しておくことに 便なプロファイラ れ , オプジェクトの消去は Delete ( ) て、行わ の生成はすべて関数 NewBySize( ) て、行わ オプジェクトレンズて、は , オプジェクト す。 数の呼び出された回数を記録するツールて、 なツールが付属しています。これはある関 の C コンパイラにはプロファイラという便利 I-C 86 ver 3.30 試食版を使用しました。 今回のトランププログラムの作成には LS 解て、きると思います。 ログラムといっても C なのて、すから容易に理 さえ理解すれば , オプジェクトレンズのプ object. h のマクロ展開と関数名のつけかた 照してください は付録ディスク内のソースプログラムを参 (t) , 審判 (Judge) などを作りました。詳しく 名前 (Name), ランク (Rank), スート (Su オプジェクト ? ) として , ゲーム (Game) , そろってきました。このほかの登場人物 ( 登場 プレイヤー (Player) とオプジェクトたちが カード (Card), 山 (Deck), 手 (Hand), player. h プレイヤー 1 : Component(PLAYER) Hand hand : Name name ; Interface(PIayer) 円 ayer NewPlayer(Name) ; Hand0fPlayer(Player) ; Hand Name0fPIayer(Player) ; Name Nothing DeletePlayer(Player) ; 8 : 7 : 6 : 5 : 4 : 3 : 2 : List きました。 調べていくっか重大なバグをとることがて、 し回数は等しくなるはずて、す。この対応を 点て、は NewBySize( ) と DeIete( ) の呼び出 れます。すなわちプログラムが終了した時 付録ディスクにはオプジェクトレンズの 付ティスクの使い方 う関数の一覧を作成してみましよう。そ ラムを見直して , ある構造体とそれを扱 ■【実践】これまであなたが書いたプログ を作りますか。 てみましよう。あなたならどんな構造体 「いろはカルタ」に変えて構造体を設計し ■【練習】問題 1 の「トランプのカード」を ・実践課題 す。 ソースて、遊んて、いただきたいと思っていま のサンプルプログラムはゲームて、遊ぶより , どおもしろいゲームて、はありません。今回 ものて、す。はっきりいってそれて、遊べるほ いカードを出した方が勝ち , という単純な に手持ちのカードを一枚ずつ出し合って強 ゲーム自体はコンヒ。ュータと人間が同時 て、表示しますのて、 , 機種依存はありません。 まります。カードの表示などもすべて文字 ヒ。ュータを相手にしたトランプゲームが始 e が作られます。 game を実行すれば , コン におき , そこて、 make を実行すれ xgame. ex すべてのソースをひとつのディレクトリ ています。 ランプゲームに関連する全ソースが含まれ 基本ファイル object. h と object. c のほか , ト れは実世界の何をコンピュータの世界に 移したものですか。 ■【挑戦】ゲームには乱数がつきものです。 乱数発生装置というオプジェクト Random を設計しオプジェクトレンズコーディ ング規約に従って random. h と random. c を 作ってみましよう。完成したら deck. c 内の 関数 ShuffIeDeck ( 収録ディスク参照 ) が R andom を利用するように修正してみまし ■【応用】本文に登場したオプジェクトた ちを C 十十で記述してみましよう。 今回は「集める」というテーマて、オプジェ クト指向について考えました。あなたの解 こうとしている問題にはどんなオプジェク トが登場するて、しようか。そのオプジェク トは何て、て、きており , どんな風に使うもの . て、しようか。オプジェクトという観点 , す なわちコンポーネントとインタフェイスを 軸に問題を見直すことは問題の整理のひと つの方法て、あることがおわかりになったて、 しようか。 さて , 次回は仮想化というテーマをとり あげましよう。仮想記憶とか仮想データ型 とか , コンヒ。ュータの世界て、は「仮想」とい う言葉がよく登場します。「仮想化」とは , 実際には〇〇て、はないのに , あたかも〇〇 て、あるかのように見なすことて、す。次回は この「見なす」ことをめぐってプログラミン グの工ッセンスを探ってみることにします。 次ロは どうぞお楽しみに プログラミングの工ッセンス 59

7. 月刊 C MAGAZINE 1992年6月号

川Ⅱ 9 0 eck から Hand に移す必要が生じます。オプ ... lnterface (Card) の部分が構造体の宣言に のインタフェイスは「カードの山」というオ ジェクトレンズて、はそれを Get 何々 ( ) と Giv なり , lnteface (Card) 以降は関数のプロト プジェクトのインタフェイスにしようと思 e 何々 ( ) という名前の関数て、表現していま タイプ宣言になることがわかると思います。 こには入れません。 うのて、 , オプジェクト名の Card はカードを表わす構 す。 カードのオプジェクト化 「山からカードをひとつもらい」という部 造体 struct CARD へのポインタ型になりま 分は GetCardOfDeck ( ) という関数て、実現し す。 以上て、「カード」を表すオプジェクト Card ます。「カードを手に加える」という部分は さてカードのコンポーネントとインタフ GiveCardToHand( ) という関数て、実現しま が作られました (Fig. 2 参照 ) 。 ェイスを考えたところてプログラムにして す。ちなみに「プレイヤーの手」という部分 みましようか。 List 3 が , カードをオプジェ は , HandOfPIayer( ) という関数て、実現し クトにするヘッダファイルて、す。カードの 山手 ています。 データは , スートとランクがそれぞれ Suit と 一枚の「カード」がオプジェクトになった Get 何々 ( ) や Give 何々 ( ) て、所有権が移動 Rank というメンバになっています。またス ところて、 , カードの集合を作りましよう。 します。たとえば上の例て、はカードの所有 ートとランクのそれぞれの値を得るインタ プレイヤーにカードを配る元の「山」と , ゲ 権が山から手に移動しています。あるオプ フェイスは , SuitOfCard と RankOfCard と ジェクトの所有権とは , そのオプジェクト ームのプレイヤーが持つ「手」をそれぞれオ して記述されていることがわかると思いま を DeIete する責任のことて、す。たとえばプロ プジェクトにしましよう。山は Deck, 手は す。カードの作成と削除はそれぞれ NewCa グラム終了時に DeIeteHand( ) が実行されま Hand とします (List 5 , List 6 , Fig. 3 参照 ) 。 rd と DeIeteCard て、す。このほかにもいくつ すが , そのとき DeleteHand() の中て、は , そ こて、 , オプジェクトの所有権の問題が生 かインタフェイスが増えていますが , 解説 れまて、に GiveCardToHand( ) 経由て所有権 じます。「山からカードを一枚もらい , その は割愛します。 が渡されたカードについて責任を持って De カードをプレイヤーの手に加える」という List 3 に記述されている Card というオプ 操作を実現しようとすると , Deck のコンポ IeteCard() してあげる必要があります。 ジェクトは C のプリプロセッサによって Lis Get や Give の有無に注意してください。た ーネントの Card オプジェクトをひとつ , D t 4 へと展開されます 0Component(CARD) ca 「 d. h の展開結果 card. h カード 4 List List 1 : typedef struct CARD ( Suit suit; 2 : rank : Rank 3 : 4 : } *Card : NewCard(Suit, Rank) : Card 5 : De 1 eteCard (Card) ; VOid 6 : Rank0fCard (Card) ; Rank Suit0fCard(Card) ; Suit 8 : B001 ean I s SameCard (Card, Card) : 9 : B001 ean I sJokerCard (Card) ; 10 : GetNameOfCard(Card) : Name 11 : 1 : Component(CARD) suit; Suit 2 : Rank rank ; 3 : 4 : Interface(Card) NewCard(Suit, Rank) ; Card 5 : N0thing DeIeteCard(Card) ; 6 : Rank0fCard(Card) : Rank 7 : Suit0fCard(Card) ; Suit 8 : BooIean IsSameCard(Card, Card) ; 9 : Boolean IsJokerCard(Card) : 10 : GetNameOfCard(Card) : Name List hand. h 「手」 deck. h カードの「山」 List 1 : Component(DECK) Card card [MAX—DECK] : 2 : I nteger tOP : 3 : 4 : Interface(Deck) NewDeck(N0thing) : Deck ShuffleDeck(Deck) ; Deck 6 : GetCardOfDeck(Deck) : Card 7 : B001ean IsEmptYDeck(Deck) : 8 : lnteger MaxDeck(Deck) : 9 : N0thing DeleteDeck(Deck) : 10 : 1 : Component(HAND) card - card [MAX-HAND] : 2 : 3 : lnteger count; 4 : Interface(Hand) NewHand(Nothing) : 5 : Hand Nothing DeleteHand(Hand) : 6 : G i veCardToHand (Hand, Card) : 7 : Hand Ge tCardFromHand ( Hand, Card) : 8 : Card GetIndexedCardFromHand(Hand, lnteger) : 9 : Card lnteger MaxHand(Hand) ; lnteger CountHand(Hand) : GetName0fHand(Hand) : 12 : Name プログラミングの工ッセンス 57

8. 月刊 C MAGAZINE 1992年6月号

き ro 「 R 叩 0 ⅲ叩 ln C + + は必要て、す。それは普通の関数の性格を持 っているのて、 , atexit ( ) の引数にすること がて、きるのて、す。 terminate マニヒ。ュレータのすべきこと は , exit( ) をコールすることによってリス ト中のすべての ioserror オプジェクトの内容 をダンプすることて、す。 exit ( ) が実行され るとき , 前に atexit ( ) を使って登録してお : dump ( ) 関数がコールされる いた eh list : 結果 , このダンプが行なわれます。コンパ IOSTEST. CPP List 7 ioserror のテスト 1 : 〃 : IOSTEST. CPP ー イラに文句を言われないために , ostream を ジェクトに応じてコールする関数を判断す 関数て、す。コンパイラは , そのときのオプ r オプジェクトの dump() は , それぞれ別の ありません。 eh list の dump( ) と各 ioserro の dump ( ) をコールします。これは再帰て、は に辿って , それぞれの ioserror オプジェクト eh list : :dump() 関数は , リストを順々 の後て、はこの return は実行されません。 返すようになっていますが , もちろん exit ( ) 15 : } : 23 : } ; 30 : } ; 44 : } 57 : } 5 : 8 : 10 : 12 : 13 : 14 : 20 : 21 : 22 : 24 : 28 : 29 : 31 : 33 : 34 : 35 : 37 : 38 : 40 : 41 : 42 : 43 : 45 : 48 : 49 : 50 : 51 : 52 : 53 : 54 : 55 : 56 : error くく” OOPS ! number: void test() ( 11 : public: static ioserror error; 9 : class go { 2 : / /. マニピュレータ terminate が使われたときや exit ( ) がコール 3 : / /. されたときにはつねにすべてのクラスのエラーが報告される 4 : / /. ることを , デモンストレーションするテストプログラム . 7 : #include く conio. h 〉″ getch() を使うため 6 : #include く stdlib. h> #include ” ioserror. h ” ”くく 47 くく nl 17 : class ( static ioserror error; 19 : public: void test() { error くく” ack! value: ”くく 3.14159 くく nl 25 : class urp { 26 : publ ic: fr iend os trearn& operator くく ()S tream& OS return OS くく” Urp! ”くく nl ; } / / メンバ関数ではない ! ! 32 : / / s ね tic なデータメンパの定義 ioserror gonk: : error("gonk") ; ioserror :error("boink ” ) ; 36 : / / グローバルな error 関数 . i oserror error ( tes ; 39 : VOid quit—test(int index) { cout くく” rly exit: press ESC ”くく endl; if(getch() = 27 ) error くく” quitting on index くく nl くく terminate; 46 : main() ( gonk g ; boink b; urp u; quit—test(l) ; g. test(); quit-test(2) ; b. test() ; quit—test(3) ; error くく u; cout くく” preparing tO exit() ”くく endl; ”くく index 〃 urp でオーパロードされているくく演算子 るのて、 , 違うクラスに同名の関数があって も混乱することはありません oioserror : dump( ) 関数 (IOSERRO. H 中に定義 ) は , メ ッセージが msg ノヾッフアにあれば , clog に情 報を出力するだけて、す。そのときは , クラ ス名と文字列 " error : " に続いて , msg バッ フアの内容が clog に送られます。 しかし , これて、終わりて、はありません。 msgz** ッフアにメッセージをどうやって入れ ますか ? この点を理解するためには , 10 SERROR. CPP の中にある , ioserror のコン ストラクタを見てください。コンストラク タイニシャライザとして最初に , べースク ラスのコンストラクタを osrstream(msg,b size) て、コールしています。 ostrstream によ って , iostream のようなものがメモリ上の 領域として作られます。この形のコンスト ラクタを使うと , 使用するメモリをプログ ラマが指定て、きるのて、す ( 引数のないコンス トラクタて、は , オプジェクトがメモリを自 分て、動的に確保する ) 。 こて、は , ostrstream に , 出力はノヾッフ ア msg へ送れ ( そしてバッフアのサイズる siz e をオーパフローしないように ) , と指定し ています。そこて、たとえば , error という名 の ioserror オプシェクトカすあって , error く < "hello" と書くと , 文字列 ' ' hello " がそのオプ ジェクトの msg バッフアに入れられるのて、 す。 loserror をテストする IOSTEST. CPP (List 7 ) は , ioserror のテ ように扱われていることが , お分かりて、し ジェクトが iostream の場合とまったく同じ メンバ関数 test ( ) の中て、は , いろんなオプ というようにしてください とにメモリを使わず , クラス全体て、一つ , トは , つねに static にして , オプジェクトご す。自作のクラスの中て、 ioserror オプジェク は , static な ioserror オプジェクトがありま やり方を示します。クラス gonk と boink に ストと , それを新たなクラスて、使うときの 20 C MAGAZINE 1992 6

9. 月刊 C MAGAZINE 1992年6月号

リが確保される , という ことがありません。 これが , とても重要なことて、す。 static なメン / ヾは , そのクラスの外て、実コ ードとしての定義と初期化が必要て、す〔ク ラス定義 ( といラよりもクラスの宣言 ) の 中に書かれているだけて、はメモリ上に実在 化しない〕。このように , 定義を別途書くこ とによってメモリが確保されますから , 定 義がどこにもないとリンカが未定義シンボ ルエラーを出します。すなわち , 定義によ って static なメンノヾのためのメモリが ( たっ たーっの場所に ) 確保され , main() が始ま る前にそのメンノヾのためのコンストラクタ がコールされます〔その static なメンノヾがユ ーザ定義型て、あった場合〕。 また , main ( ) が終了する前にはデストラ クタがコールされます〔その static なメンバ がユーザ定義型て、あった場合〕。 static なメ ンバを定義し初期化するときは , ERRTES T. CPP の中にもあるように , 必ず、 いうスコープ指定演算子を書いてください : demonstration ( ) の中て、は , term etest : inate() をちょうど printf ( ) と同じゃり方て、 コールしています。こういう簡単な使い方 が , この error handler クラスを作ったねら いて、す。 iostream ふうの方法 ェラーメッセージを printf( ) ふうに表示す るやり方は , クラスのユーザが C 十十になじ みが薄いときには , とくに有効て、す。しか し , printf ( ) 的なやり方には , 可変引数リス トの扱いに関して幾つかの間題があります。 〃は単に、、引数の個数が不定〃 という意味なのて、 , これに関して渡される 引数に関しては , コンパイラは〔関数プロ トタイプに照らしての〕データ型のチェッ クがて、きません。さらに , 書式指定文字列 を正しく書かなかったり , 逆にリスト中の 引数の個数が書式指定文字列の指定より少 ないときには , ごみがプリントされてしま うて、しよう。どちらの場合も , クという / ; 10St 「 eam を使った書き方 cout くく” hello, world, l'm ”くく 5 くく” on ' くく ctime(&now) くく endl; 8 : 9 : 10 : 12 : 13 : 14 : 15 : 16 : 17 : 20 : 22 : 23 : 24 : 26 : 29 : 30 : List 5 IOSERROR. H iostre 囲を使う簡単なエラーハンドラ . 1 : / / : IOSERROR. H ー 2 : / /. 他のクラスやプログラムの中にこのクラスの static な 3 : / /. オブジェクトをインストールすることによってエラー 4 : / /. 処理の基本枠組みが設定されるので , 後でもっと高度 5 : / /. なものに変更して再コンパイルすることもできる . 6 : #ifndef IOSERROR_H_ 7 : #define IOSERROR_H #include く iostream. h 〉 #include く strstream. h> 11 : 〃このマニピュレータは出力ストリームに改行を挿入する : inline ostream& nl(ostream& (S) ( return OS くく” \ Ⅱ” ; ) class ioserror : publ ic ostrstream { 〃このクラスの”ローカルな const ” enum { bsize = 300 } ; char msgCbs ize] : 〃ェラーメッセーシ・を入れるためのハ・ツファ char* classname; 〃このハント・ラを使うクラスの名前 18 : public: ioserror(char * class—name) ; 〃クラスの名前 void dump() { if(*msg) { 〃メッセージがあればダンプする 27 : 〃このマニピュレータはエラーメッセージをすべてプリントして clog くく nl くく classname くく " error: " くく msg くく endl; 31 : #endif / / IOSERROR_H_ ostream& terminate(ostream& (S) ; 28 : / / プログラムを終了させる : 意図的に曖昧な指定によって , コンパイラ にはチェックの道が閉ざされています。引 数リストを正しく書くことが , もつばらプ ログラマの責任になってきます。 printf ( ) の こういう問題点だけて、も十分に厄介て、すが , わけの分からないエラーメッセージを読ま される側は , なおさら困ります。 この , I / O て、データ型チェックが行われな いという問題点が , C 十十の標準ライプラリ として iostream が作られた理由の一つて、 す oiostream を使うと ,List 4 のような書き 方がて、きます。 cout というオプジェクトは , C の標準出力 stdout とよく似たものて、 , < < という演算子 は , ストリームオプジェクトに対して使っ たときには、、出力せよ〃という意味になるよ う , オーバロードされています。 < < 演算子 の間の式は , ーっひとっ別々に評価されま す 0List 4 の実行文の中て、は , 実にいろんな ことが行なわれます。文字列が二つあるし , 文字列を返す ctime ( ) という (ANSIC の ) 関 数コールがある。そして整数 ( 5 ) があり , マ ニピュレータと呼ばれるものの一種てある endl があります。マニヒ。ュレータとは , 単な る出力て、はなく , 何か別の処理を伴ってス トリームに出力するもののことて、す。この endl というマニヒ。ュレータは , ストリームに 改行を出力してから , そのストリームをフ ラッシュします〔訳注 : Borland C 十十 2. 0 の日本語マニュアルは , マニヒ。ュレータ (manipulator) に、、処理子〃の訳語を与えてい ます (PROGRAMMER' S GUIDE, pp. 22 引数が , 文字列か , int か , float か , 等々 に応じて , それぞれ違う関数がコールされ ます。そういう内蔵データ型だけてなく , 自分が新たに作るクラスの中に独自の iostr eam 関数を定義して , そのクラスのオプジェ C 十十でエラー表示を楽にやろう 17

10. 月刊 C MAGAZINE 1992年6月号

よう。メンノヾの error オプジェクトに情報が 送られるたびに , その情報はストアされま す。そして terminate 関数がコールされたと きだけ ( あるいはプログラムが正常終了によ って , または明示的に , exit ( ) て、終了した とき ) , すべてのメッセージがプリントされ ます。 クラス urp は , iostream operator< < ( ) を自作のクラス用にオーバロードするやり 方を示しています。 main ( ) の中て、 , error < < u という形て、出力演算子をコールしていま す。通常の iostream に対してて、きることは すべて , ioserror オプジェクトに対してもて、 きます。クラス ioserror は iostream の一つの タイプ〔 ostrstream 〕から派生しています から , 自身もまた iostream の一つのタイプ て、す。したがって , 自作のクラスのために ストリーム出力演算子 < くをオーバロード すると , その演算子は ioserror オプジェクト に対しても使えるのて、す。これは , よーく 考えないと理解て、きないかもしれませんが , 実際に書くコード量は少なく , そしてしか も iostream のパワーをすべて利用すること がて、きるのて、す。 あなたが作るクラスのすべてに , ioserro r オプジェクトを入れるようにすれば , 統一 的て、ポータブルなエラー報告の枠組みが確 立します。そのメンバオプジェクトは stati c て、すから , ioserror をメンバとして含むオ プジェクトのサイズや実行性能には , 全然 影響を及ばしません。すべてのエラー報告 が , 完成度の高い iostream の形式を使え , iostream 用に operator < < ( ) 関数をオーノヾ ロードした新たなクラスも , そのまますぐ , コラムコメントの書き方 このエラー報告機能を使えます。さらに 工ラー処理のやり方を変更してもっと強力 にしたいときて、も , 1 か所だけ変更すれば , 単純に再コンパイルするだけて、 , その変更 を全体に波及させることがて、きるのて、す。 Bruce Eckel 氏は『 C 十十 lnside & Out 』 (Os born/McGraw-HilI, 1992 , fUsing C 十十』 の改訂版 ) の著者で , 本稿は同書からの抜粋 を元にしています。彼は「 C 十十 ReportJ 誌の特別編集者であり , C 十十と ' 87 年から 関わっています。彼は , C 十十のコンサルテ イングを専門とする , RevoIution2 という会 社のオーナーでもあります。 私は , プログラムにコメントを書くとき , ある簡単なシンタ クスを守るようにしています。各ファイルの冒頭にヘッドライ ンがあり , それは〃 : て始まります。プリントアウトしたもの の上てファイル名が分かるように , 〃 : の直後にはファイル名を 必ず書きます。ファイル名に続けてダッシュを書き , その後に ファイルの簡単な説明を書きます。 2 行目以降の , / /. て始まる行 は , そのファイルに関する追加的なコメントてす。 プログラムの文書化のためのその他の方法は , 特殊なプリプ ロセッサを要するものだったり (DonaIdKnuth の WEB など ), ( 私の以前の試作品のように ) 複雑すぎて簡単に覚えられないも のだったりします。 っていますし , Borland C 十十にも優れた grep がオマケて付い 使い始められることてす。 grep のいろいろなバージョンが出回 このコメントの書き方シンタクスの良い点は , 誰もがすぐに きっと , 容易に採用て、きることてしよう・。 私のような怠け者ても好きになれたぐらいてすから , あなたも ーズを持つ人 ( すなわち私 ) のためて、す。このやり方は , 発したのは , ソースコードからコメントを簡単に取り出したい いう方法の改良版も , やはりだめてす。私がこのスタイルを開 いう状況の中ては , そういう難しい方法は流行りません。そう コメントを書きたがらないプログラマがあまりにも多い , と てきます。 grep を使ってファイルのヘッダブロックの情報を取 り出すためには , こんなコマンドを使います。 AWK の処理系のどれかをお持ちなら , もっとうまいやり方が て、きます。 AWK は , C ふうのシンタクスを持ったテキスト処理 用のインタブリタて、すが , PC 用には RobDuff 作の PCAWK を , いろんな BBS から無料て入手て、きます。ーっのディレクトリに あるすべてのファイルの , コメントヘッダ部分を取り出すため には , 次のコマンドを使います。 コマンドの意味は , substr($0, 5 ) print / / : またはい ) / /. のある行 ・・・全行 ( $ 0 ) の 5 文字目以降 ( , 5 ) を ・・・出力せよ AWK の substr() 関数をこのように使うと , コメントの頭の部 分 ( 〃 : など ) を排除して , 文章だけを取り出せます。ヘッドラ インだけが欲しいときは , 次のコマンドてす。 awk " / \ / \ / : /{print substr($0, 5 ) } " *. h *. cpp AWK は , プログラマにとって素晴らしいツールてす。いろん な便利な使い方を学ぶたびに , ますますみなさんにも推奨した くなります。 C + + でエラー表示を楽にやろう 21