#include くファイル名 > システムて、定められたディレクトリて、 ファイルを探す #include " ファイル名 " カレントディレクトリて、ファイルを探 す というふたつの書き方があるのて、す。自分 て、つくったヘッダファイルを取り込むとき には , 後のタイプの # include を使います。 本来ならば「変数」の解説 ( ' 89 年 11 月 て、お話すべき内容だったのて、すが , 関数に ついての知識がないと実感て、きないのて、は ないかと思い , 今回まて、延期してきました。 6-6-1 記憶クラス C て、は変数や関数の性格を決定するために データ型と記憶クラスを用います。型はデ ータのサイズを規定するのに対し , 記憶ク うのもひとつの手段かもしれません。この プログラムにも出てくるような決まり切っ 点については , 機会があった時点て、解説し ラスはデータがメモリ上のどこにとられる た処理が出てくることがあるかと思います。 かを規定するものて、す。さらに記憶クラス ましよう。 そのような処理は関数にしてしまい , 独立 の指定がされている位置 ( 関数の内とか外 ) ところて、①のファイルて、 # include が見な したモジュールにしてしまったらいかがて、 しようか ? さらに本誌 ' 89 年 11 月号の特集 によって , その変数の通用範囲 ( スコープ ) れない形をしていますね。実は今まて、出て が決まります。 にもあったようにライプラリを作ってしま きませんて、したが , 変数の宣言は 型宣言子変数名の並び Fig. 3 記憶クラスを考慮した関数の形 という形て、行うという話は以前にしました 〇タイプ 1 が , 今回記憶クラスを導入しこれを拡張し てみましよう。 関数名 ( 引数型宣言並び ) 記憶クラス指定子型名 記憶クラス指定子型宣言子変数名の 関数内変数宣言 並び このようにいちばん先頭に記憶クラスの 実行文 指定をします。関数に記憶クラスを指定す る方法が Fig. 3 て、す ( タイプ 1 のみ記述 ) 。ま た , 記憶クラス指定子の種類を Fig. 4 に示し 6-6-2 変数の記憶クラスと 範囲 変数は記憶クラス指定子と宣言の位置に よって通用範囲が決まります。記憶クラス SpIit1 'a 2 : 分割コンパイルの例 3 : ma i n 関数部分 5 : #include く stdio. h> 6 : #include ” splitl ・ . h" 7 : 8 : void main(void) 12 : 13 : } / * sp ⅱ t 1 ・ . h を読み込む * / printf(" 1 + 2 +. summation(n)); . + %d = XdYn ” Split1'b 分割コンパイルの例 2 : 総和の計算をする関数部分 3 : int summation(int n) 5 : 7 : int i, tmp for (i 9 : tmp 十ニ i : return (tmp) : Fig. 4 記憶クラス指定子の種類 スタック記憶クラス指定子 autO レジスタ記憶クラス指定子 register 静的記憶クラス指定子 static 外部記憶クラス指定子 extern 型定義指定子 typedef ※ typedef は , 構文的な都合から記憶クラス指定子と呼はれる 機能は異なり , 型の同義語を作る 120 CMAGAZINE 19 3
C 十十 プログラミング 入門 コラム 2 補助クラス (Parameterized CIass) C 十十の設計者である Stroustrup は , 近い将来 C 十十 に容易に汎用クラスや容器クラス (Container Class) が 記述できる補助クラスの機能を導入すると述べてい る。これについては , いくつかの文献 ( 参考文献 [ 2 ] [ 4 ] ) で紹介されている。これによって , C 十十はコ ードの共有が可能な大規模なライプラリの記述に優 れた新しい機能を手に入れるであろう。 補助クラスは , キーワード template により以下のよ うに宣言される。 template く class PT > 補助クラスの名前は PT であり , これを使った汎用 スタック型は以下のように定義される。 class stack { private: PT * s; public: stack(ulong); stack(stack < PT > ) ; 名は , プリプロセッサて、処理されるため , 複雑なも の ( たとえば , ポインタなど ) は使用て、きません。そ の場合には , typedef により別名をつけます。 ロおわりに 今回は比較的有名なデータ構造をクラスて、実装す る例を示しながら , 実際のクラスの設計およびその 汎用版の作成を解説しました。これらの例はクラス 設計の適当な例題として取り上げましたが , すて、に oops や libg 十十て、優れたものが実装されています。そ れらについては , この連載の最後に紹介します。 コラム 3 genclass 現在ある処理系 ( release 2.0 以前 ) では , 汎用クラ く T >stack( く T >stack&); ス (Generic Class) を効率よく記述することはできな い。将来の機能 (Parameterized Class) では , これを簡 く T > pop(void); 単に記述できるであろうが , 今はマクロによる方法 などで我慢するしかない。 GN U C 十十ライプラリでは genclass プログラムによる興味深いアプローチを行っ ている。 genclass は特定の型についての容器クラスを 生成するが , その方法は Stroustrup のマクロによるも のではなく , Parameterized Class に近い記述を許して いる。そのため , 今後の修正は最小限に抑えられる。 Parameterized CIass 同様 , 擬似の型 (Pseude Type) の 表現にはく T > やく C > が使われる。以下にクラスス タックの例を示す。 class く T >stack { private: く T > * s; public: く T >stack(ulong); PT pop(void); PT stack く PT > : :pop(void) この型は以下のように使用できる。 stack く string > s1; stack く int> s2; このおかしな型名 ( たとえば stack く string > ) は typedef により別の名前にすることもできる。 この機能により , C 十十のプログラムのコード量は ドラスチックに減少することが予想される。また , コラム 3 で紹介するが , GNU では , 現状からこの機能 にスムーズに移行できるように genclass プログラムを 提供している。 参考文献 [ 1 ] Stroustrup , B. , 、、 The C 十十 Programmn ing Language" , Addison-Wesley , 1986. [ 2 ] Lippman, S. B. 、、 The C 十十 Primer Addison-Wesley , 1989. [ 3 ] Lea, D. , 、、 User's Guide to GNU C 十十 Library" , Free Software Foundation , 1989. [ 4 ] Stroustrup , B. 、、 Parameterized Types for C 十十〃 , ln Proc. USENIX C 十十 Confer ence, 1988. : POP(VOid) { く T > く T > stack : そして , genclass は以下のようにして , int 型のスタ ックを生成する。 ・ % genclass int val stack Creturn] 第 1 引数は取り扱う型 , 第 2 引数は v 引か ref のどちら かを指定する。第 2 引数の値は扱うデータの関数呼 び出しの型式を指定し , 前者では値による呼び出し , 後者では参照による呼び出しが使われる。最後の引 数は , もとになるクラスの名前である。この場合 ファイル int. stack. cc および int. stack. h に intstack という 名前のクラスが生成される。 88 CMAGAZINE 19 3
クの先頭部分 s [ P ] に格納し , p の値をインクリメン トします。 stack : : POP(V0id) て、は , p の値をデクリ メントし , s CP] に格納されている値を戻り値とし て返します。 それらの操作て、は , 準備された領域のみて、処理さ れるように検査もされます。つまり , push 操作の領 域の上限 (s CS 工 ZE]) , pop 操作の下限 (s CO]) を越 えた書き込みや読み出しは不当な処理とみなされま す。コンストラクタ stack::stack(void) はたんに p の値を 0 に初期化するだけて、す。これて、 , いちおうク ラス stack は完成しました。これを使って Fig. 3 て、行 った処理を記述すると List4 のようになります。 ロ乍成 試作版は完成しましたが , これはあまり実用的て、 はありません。スタック領域を静的 ( static ) に確保す るため , 記憶空間はムダに費やされます。用意され た操作もわずか 3 種類だけて、す。ここて、は , これらの ことに注目して改良を加えます。 C + + プログラミング入門 85 ポインタになり ( 実際の両者の違いは , 領域の確保が メンバ s が , 演算子 new により確保された領域をさす 試作版との違いは , スタック領域の名前て、あった ulong Size ; ulong p ; TYPE* s ; class Stack { 以下のように変更します。 照 ) 。それらを利用するため , クラス Stack の定義を 子 new/delete があります ( 1990 年 1 月号のコラム参 C 十十には , 記憶空間を動的に使用するための演算 保することにします。 す。そのため , スタック領域を動的 ( Dynamic ) に確 与えられるため , 領域の多くの部分がムダになりま 情報しか扱わないスタックにも同じ大きさの領域が ば , 扱う情報の数量を 10000 と決めると , 数量 100 の たのて、は , 記憶空間はムダに消費されます。たとえ 試作版のように static にスタック領域を確保してい コラム 1 スコープ ( scope ) 演算子 スコープ演算子 ( : : ) はスコープを強制的に変更す る演算子で , もっとも多い使用法はメンバ関数の定 義であろう。以下にメンバ関数の定義例を示す。 char * bignum : : name(void) { / * これはスコープを bignum に変更し , name 関数は ふつうの関数ではなく bignum のメンノヾ関数であるこ とを指示している。これがクラスの定義のなかであ れば , スコープの変更が必要ないため , 以下のよう 3 〃 print num ・ 2 { int num = 0 ; 1 int num = 256 ; に例を示す。 て隠された大域変数のアクセスを可能にする。以下 また , スコープ演算子は , 同名の局所変数によっ char * name(void) { / * public : class bignum { に記述できる。 ときに使える。たとえば , 関数から基本クラスの同名のメンバ関数を呼び出す の値は 256 になる。この方法は , 派生クラスのメンバ 照は不可能であるが , スコープ演算子を使えば : : num で , その値は 0 である。ふつう■行目の num の値の参 この場合の 3 行目では , num は 2 行目で宣言したもの では , 3 行目では , 自分自身を呼び出してしまい , 終 わりのない再帰呼び出しにおちいってしまう。しか し , 次のようにスコープ演算子を使って基本クラス の関数を指定すれば問題はない。 3 return bignum : : test(num) / num ; AT&T の release 2.0 で追加された多重継承でも , ど の基本クラスのメンバ関数を呼び出すかはこの方法 で指定される。以下に例を示す。 class f00 : takO, ika { public : char * test(num) { { tako : : test(num); } if (hint) else { ika : : test(num); } 1 2 3 char * signedbignum : : test(num) { return test(num) / num ; そのほかに , スコープ演算子は基本クラスを非公 開に派生したクラスで , 基本クラスの一部のメンバ を公開メンバにするのに使われる。以下に例を示す。 class unsignedBignum : bignum { public : bignum : : operator= 基本クラス bignum は , 派生クラス unsigned Bignum に非公開メンバとして派生しているが , スコ ープ演算子により公開にされた bignum : : opera tor=( ) は , unsignedBignum クラスの公開メンノヾと して扱われる。
るが , 潜在的な問題がひとつある。それは , 基本クラス hashtab 型のポインタや参照さ れた変数を使用するとき , 導出クラスのな List 4 HASHTAB . HPP かて、同じ名前のメンバ関数は使用て、きない ということて、ある。基本クラスて、関数を仮 想しておき , この問題をうまく避けようと 1 : #define DEBUG_HASHTAB 3 : / / hashtab. hpp - ノ、ツシュテ 4 : class hashtab { size_t tablesize; S i ze_t inserted; void **table; 23 : 20 : 9 : 8 : 7 : 6 : 5 : ープルのクラス定義 / / 実際のテープル / / 挿入した要素の数 / / テープルのサイズ s ize-t (*map) (VOi d *element) : int (*compare) (void *el, void *e2) : size-t -lookup(void *element) : 12 : public: hashtab(size-t tablesize, si ze-t (*map) (void *e lement) . int (*compare) (void *el, void *e2)) : / / キーからテープルのインデックス への写像関数 / / strcmp にしたがってふたつの要素を比較 / / 内部検索関数 hashtab()o (d) { delete table: void *lookup(void *element) : VOid insert(void *elenent) : VOid remove (VOid *e lement) : 22 : # if defined (DEBUG_HASHTAB) & & def ined (__ZTC__) void dump-tab(void (*d) (void (e)) : 24 : #endif List 5 HASHTEST. CPP / / ほとんどの部分のデータ型をこれで修正できる typedef unsigned size_t; #include く stream. h> 4 : #if !defined(_-ZTC--) 2 : / / hashtest. cpp ー汎用ハッシュテープルクラスを試す 3 : 5 : 6 : 9 : 12 : 15 : 19 : 20 : 22 : 23 : 24 : 30 : 33 : 13 : #include "hashtab. hpp" #include く ctype. h> 10 : #end i f #include く stream. hpp> 8 : #include く stdio. h> 7 : # se / / データ型 size ー t を用いる size-t imap(void *element) { return *((int *)element) : int icompare(void *el,void *e2) { i f ← ( i nt * ) e 1 > * ( i nt * ) e2) return 1 : else if(*(int の el 32 : #endif cout くく *(int の el : 29 : void idisplay(void *el) { 28 : # if defined (DEBUG_HASHTAB) & & defined (__ZTC__) return -1 : e ー se return 0 : しても , C 十十て、は導出クラスのなかて、仮想 関数の型の再定義をすることはてきない したがって , 導出したハッシュテープル のオプジェクトをさすポインタを , ハッシ ュテープルのポインタとして割り付けて使 用することはて、きない。しかしながら , の禁止は煩わしいものて、はない。それは , 一般的にハッシュテープルの実体 ( ins tance, Smalltalk ー 80 て、はインタンスと呼ぶ ) に対して多様なアクセスを必要としないか らて、ある。多様なアクセスを行う理由を ( へ そ曲がりて、なく ) 考えついたならば , ぜひ教 えていただきたい inttab クラスはありふれた例て、ある。オ プジェクトの中のデータがキー ( key ) だけの 場合 , LOOKUP 関数はプーリアン関数 (Boolean function) に退化する。そのとき はテープルから何も検索しない。つまり , 格納されているものが検索のために与えら れたキーそのものだけなのて、 , すて、にその キーがあるかどうかを調べる ( 0 か 1 かを返す ) データを検索することにも , 複合的な hash る。複数のキーを用いてテープルから対象 class) に hashtab を変えていくこともてき ている結合配列クラス (associative array graming LanguageJ ( 文献 [ 2 ] ) て、論じられ てきる。 Bjarne Stroustruph の「 C 十十 Pro ータとして ,hashtab の変数を使うことも イスをもっクラスのためのプライベートデ する代わりに , まったく異なるインタフェ tab から新しい導出クラス (subclass) を導出 クラスの使用法にこだわる必要はない。 hash - とはいっても , 筆者が例示した hashtab れる絶好の例なのて、 , あえて用いた。 クラスを用いてて、きるアイデアを与えてく いるべきて、ある。しかし , inttab は , hashtab 理想的にはメモリアロケーション技術を用 プジェクトを取り扱う場合には効率が悪い めに演算子 new を使用することは , 小さなオ ぞれのテープルのエレメントを生成するた だけて、よいというわけてある。また , それ ァープルのなかのテープル 27
ithin Tab 厄 5 隠すことがて、きるわけて、ある。 のて、はなく , オプジェクトをさすポインタ ハッシュテープルは , オプジェクトそのも めに関数ポインタを使用する方法て、ある。 て , 汎用的な関数をインプリメントするた のテクニックとしても用いられているもの 筆者が開発した解決法 ( List4 ) は , C 言語 するかなどの事項て、ある。 データが等しいかどうかをどのように決定 のように割り付けられるか , ふたつの対象 されるかとか , 対象データがアドレスにど 空や消去された対象データがどのように表 かの事項を知っておく必要がある。それは , 扱おうとするオプジェクトについて , いくっ プジェクトを取り扱うことがて、きず , 取り て , ハッシュテープルのクラスは任意のオ structer) に渡すことはて、きない。したがっ 型をパラメータとしてコンストラクタ (con このことに関して , たとえば るをえない 語てはないのて , 不可能なことだといわざ れは C 十十が純然たるオプジェクト指向の言 ことがて、きなければならない。しかし , ことなしに任意のオプジェクトを取り扱う は , 全体に一般性をもち , 内容を考慮する 理想的には , ハッシュテープルのクラス ープルのコンストラクタて、は , コンストラクタへ渡されな アドレス値への変換を行う をもっている。ふたつの対象データの比較 ノ、ツシュア くてはならない ための関数は , を行う関数と , inthashtab は基本クラス (parent class) class) のメンバ関数 (member function) CPP(List5) を示す。導出クラス (derived 使用例のひとっとして , HASH TEST. スト以外の使用法があった / ) 。 用いて素数の表を作成した ( べンチマークテ 数をみいだす方法 ) の組み直しバージョンを ネスのふるい (Eratosthenes program, 素 素数を求める。筆者は標準的なエラトステ イズからハッシングを行う適切なふたつの find tabsize 関数を呼び出し , テープルサ List 3 = NOTFOUND ? empty : table[index]) : ・ conpare(element,table[cur]) ! = の : element; table [cur] 186 : void hashtab: :dump-tab(void (*display) (void (e)) { 185 : #if defined(DEBUG_HASHTAB) & & defined(__ZTC__) table[x] = deleted; if (x ! = NOTFOUND) X 100kup (element) : exit(l); cerr くく "deletion from empty hash tableYn": if(inserted- i nt X : 174 : void hashtab: :remove(void *element) { 173 : / / テープルから要素を削除 return (index size_t index lookup (elenent) : 167 : void *hashtab: : lookup(void *element) { 166 : / / テープルの要素を検索 = empty ? NOTPOUND : cur) : return (table [cur] cur (cur + (2) % tablesize) = deleted ? 1 (table[cur] table[cur] ! = empty & & for(cur = hl; h2 = (mapped-val % (tablesize ー 2 ) ) 十 1 : 〃 2 次ハッシュ - mapped-val % tablesize; mapped-val = map(element) : / / キーから値への写像と , 1 次ハッシュを行う size-t napped-val, hl, h2,cur: 100kup (void *element) { 148 : s ize_t hashtab: 147 : / / hashtab クラスに対する検素関数プライベート部 144 : #define NOTFOUND (UINT_MAX) 183 : } 182 : 181 : 180 : 179 : 178 : 177 : 176 : 175 : 171 : 170 : } 169 : 168 : 164 : 163 : } 162 : 161 : 160 : 159 : 158 : 157 : 156 : 155 : 154 : 153 : 152 : 151 : 150 : 149 : 145 : 143 : 141 : 172 : / / 165 : / / 146 : / / 142 : } 202 : } 201 : 200 : 199 : 198 : 197 : 196 : 195 : 194 : 193 : 192 : 191 : 190 : 189 : 188 : 187 : 184 : 203 : #endif cout. put( ・ Yn') : if (i % 5 = cout. put (' e 1 S e cout. put( ・ Yn ・ ) : if (i & & (i % 5 = の ) display(table[i]); e 1 se cout くく” deleted"; = deleted) else if(table[i] cout くく "empty" = enpty) if(table[i] ー 0 : i く tablesize; i + + ) { for ( i size_t i; nashtab とは違う引数をとる。しかし , 導 出クラスを用いるのて , 基本クラスのなか に仮想的なメンバをつくる必要はない。つ まり , 再定義する代わりに , inthashtab の メンバは基本クラス関数の名前をオーバ ロードするわけだ。これはうまい方法て、あ 26 CMAGAZINE 19 3
最新 / 2 入門 大きさに変化し , 所定の座標 (NO. 1 は左 メッセージキューは PM から渡されるメッセ 下 , NO. 2 は右上 ) て、回転を始めます。この ージを格納するためのものて、す。 PM アプリ 状態て、のアイコンはタイマオン ( 回転中 ) を ケーションて、はまず最初にこの手順を必ず 意味します。さらにもう一度ダブルクリッ 己述します。 クすれば、、 Timer 0 クの表示とともに回転は WinRegisterClass( ) て、クラスを登録し 止まり , 子ウインドウは回転前と同じ位置 ます。、、クラス〃は , PM て、はとても重要な と大きさて、再表示されます。この状態て、の 概念て、正確な理解を必要としますが , いち ばん大事なことは , 同一クラスによって開 アイコンはタイマオフ ( 静止中 ) を意味しま す。この後何度も回転 , 静止を繰り返すこ かれたウインドウに対して送られたメッセ とがて、きます。子ウインドウのシステムメ ドウの現在の情報を取得するバッフアに ージはそのクラスに登録された同一ウイン ニューを選択すれば , 子ウインドウを終了 モジュールハンドルは DosLoadModule( ) ドウ関数によって処理されるということて、 させることがて、きます。このサンプルプロ により口一ドされたダイナミックリンクラ す。また , クラスに予約メモリを登録して グラムを終「させる場合は , メインウインド イプラリのハンドルになります。 おくと ( 子ウインドウのクラス登録て、行って ウのシステムメニューを選択してください 各クラスに登録されたウインドウ関数に います ) , ウインドウを開くたびにそのウィ こて、はメインウインドウ関数と子ウインド また , 子ウインドウの回転中にメインウ ンドウに対して予約したぶんの記憶域を確 インドウのサイズを変更してみてください ウ関数 ) は型やパラメータに制約があり , ソ 保してくれますのて、 , ウインドウ対応のデ 回転している子ウインドウの大きさがダイ ースにあるような型宣言を必ずしなければ ータをもちたいときに利用します。 ナミックに変化します。 いけません。 WinCreateStdWindow( ) はクラスに基 く main 部分 > づいてウインドウを開きます。標準フレー 丿スの解説 main( ) に入ると , まずフレームウインド ムウインドウを開くときにはこの API を使用 ウの作成方法を制御するオプションデータ します。戻り値としてフレームウインドウ くへッタ , および main の前まで > を宣言しています (flMainFIags, fIChiId のハンドルを取得し , 最後のパラメータと 、、 INCL DOS" , 、、 INCL WIN" , 、、 INCL Flags)0FCF 定数の組み合わせて、いろいろ GPI ク定数を宣言してから , OS2. h をインク してクライアントウインドウのハンドルを な制御オプションを指定することがて、きま ルードすることにより ,Dos, Win, Gpi の 取得します。ウインドウはつねにこのウィ す。子フレームウインドウは最大化て、きな ンドウハンドルて、管理されます。 各 API が使用可能になります。 C の標準ライ こて、は プラリを使用する場合は , さらにそれぞれ いように設定し , またメニューをつけ加え メインウインドウひとっと , それを親とし ました。 た子ウインドウのふたつを開いています。 に必要なヘッダをインクルードしてくださ 子ウインドウはつねに親ウインドウによっ い。最後に , いろいろな定数や型宣言を含 szMainClass, szChildCIass はクラス 名て、す。メインウインドウと子ウインドウ んだユーザヘッダ ( smp. h , 付録ディスクに てクリッヒ。ングされ , 親ウインドウの外に のためにそれぞれ用意されています以下 . は表示されません。 収録 ) を通常はインクルードします。 main( ) 内て、使用される変数が宣言されてい ウインドウが作成されましたら ,DosLoad main 関数やウインドウ関数から共通に参 ます。 Modu1e( ) て、アイコンリソースを含む 照される変数は main ( ) の外に宣言します。 wl, w2 は , 同一クラスの子ウインドウふ こて、はアンカープロックハンドル (hab SMPDLL. DLL というダイナミックリンク ライプラリをロードします。もしロードて、 たつに異なる動きをさせるためのローカル Main) , メインクライアントウインドウハン ドル ( h w n d M a i n ) , S W P 構造体 データ領域として使用します。 きないときは WinMessageBox( ) てその旨 (swpMain) , モジュールハンドル (hmodD 処理の最初に , WinInitialize( ) て、アンカ をメッセージボックスて、表示して , このプ ID を宣言しています。アンカープロックハ ープロックハンドルを , WinCreateMsg ログラムは終了します。 ンドル , メインクライアントウインドウノ Queue( ) てメッセージキューハンドルを受 アイコンリソースがロードてきましたら , ンドルは main( ) と子ウインドウ関数から , け取ります。アンカープロックハンドルと WinLoadP0inter( ) て , 4 つあるアイコンリ SWP 構造体はメインウインドウ関数と子ウ は , 起動されたアプリケーションを管理す ソース中の起動時のアイコン (IDI ・ ICONI) インドウ関数からモジュールハンドルは るために PM が決定するハンドルてす。複数 のポインタをロードして , ふたつある子ウ main( ) とメインウインドウ関数と子ウイン の同じアプリケーションが , 同時に動作す インドウのフレームウインドウへ , それぞ る場合がありますが , この場合のアンカー ドウ関数から参照されます。 SWP 構造体は れ WM SETICON のメッセージとともに プロックハンドルはそれぞれ異なります。 WinQueryWindowPos( ) によりそのウイン WinSendMsg( ) て、送ることによって , 子ウ 特集最新 OS / 2 入門 51 Fig. 4 ダイアログボックスエテイタ 一三ロ ⅱし嶽 ロロ国ロ国
タックを汎用性のあるものに変更したいと思います。 マクロを使ったクラススタックの汎用クラスを gstack という名前にすれば , そのマクロの定義は , 、 generic. h" を使い List7 のように記述て、きます。 マクロ name2 は , 、、 generic. h" て、定義されており , ふたつの引数を連結します。このマクロは処理系間 のプリプロセッサの違いを吸収します。ここて、は , マクロ gstack declare(type) および gstack implement(type) を定義します。ここ <gstack(type) は先頭て、 name2(type, gstack) に置換されるため , 型名を含んだ文字列になります ( たとえば ,type が int て、あニ intgstack になる ) ogstack declare(type) には通常ヘッダファイルに記述されるクラスの定義 を行い , gstack implement(type) にはそのクラスの メンバ関数の定義を行います。これらの定義は , 前 節の改良版クラス Stack とほば同じて、すが , このよう にマクロの形にすることて、 , 容易にいろいろな型に マクロによる汎用クラス gstack の実装例 32 : } : 41 : } List 7 C + + プログラミング入門 87 implement) に置換されます。なお , 引数て、与える型 ( たとえば , stringgstackdeclare, stringgstack した gstack declare(type), gstack implement(type) generic. h ' ′て、定義されており , これらは List7 て、記述 こて、使ったマクロ declare, implement は , implement(gstack, int); declare(gstack, int); うになります。 所のみて、行います。また , 型 int て、も同様て、以下のよ ルて、必要て、すが , 後者はプログラム中のどこか 1 か 前者はそのスタックを使っているすべてのファイ implement(gstack string); 以下て、与えられる。 のようにして得られ , その実際の手続きの定義は , declare(gstack, string); たとえば , 型 string を扱うスタックは , 対して使うことがて、きます。 S new type [s i ze = a. s i ze] : \ gstack (type) (gstack (type) & a) { \ S new type[si ze gstack(type)(int si = 100 ) { \ 7 : pub I i c : \ ulong size; \ ulong p; \ type* s; \ 3 : class gstack(type) { \ 2 : #define gstackdeclare(type) \ 1 : #define gstack(type) name2 (type, gstack) 4 : 6 : 8 : 9 : 20 : 24 : 25 : 26 : 28 : 29 : 30 : 34 : 35 : 37 : 39 : 40 : memcpy(), a. s, int(size * sizeof(type))) : \ gstack(type) (void) { delete[size] s; } : \ void push (type arg) { \ if (p > = size) { cerr くく "stack overflow yn" { s Cp] = arg : P 十 = 1 : } \ e 1 S e type POP ()o (d) { \ if (p く = の { cerr くく” stack underflow \ 心 1 : return s[p] : e 1 S e gstack(type)& operator (gstack (type) & ) : \ gstack (type) & operator + = (gstack (type)& a) { \ memcpy()s [p] , &a. s [ 0 ] , int(). P * sizeof(type))); \ return *this; \ 33 : #define gstackimplement(type) \ gstack(type) & gstack (type) : :operator if (this = (a) { return *this; } \ delete s; \ a. s i ze] : \ new type[size S (gstack (type) & a) { \ memcpy(), a. s, int(size * sizeof(type))): \ return *this; \
十 十 5 0 Pre—em ません。この部分の関数は C 十十て、は書けま せん。コンパイラが想定する環境 ( セグメン トレジスタの値など ) が , 正しくないことが ありうるからて、す。実際 , 割り込まれたと きのコードがスタックを限界近くまて、使っ ているかもしれないから , スタックの残り 量が十分かどうかも , わからないのて、す。 こういう関数を書くことは一種 そのため , の挑戦て、す。 しかし , それて、も私は複雑なスケジュー ラ関数をアセンプリ言語て、はなく , C 十十て、 書きたいと思いました。そのために , ある 単純な , ややずるい方法を使いました。タ イマ割り込みがステートをセープして , 別 こて、は のステートをリストアしますが , どのタスクにリターンするかは決めません。 タイマ割り込みハンドラは , つねに同じタ スクにスイッチし , そのタスクがスケジュ ーラを実行するのて、す。このスケジューラ が C 十十て、書かれており , 次にどのタスクを 走らせるかを決め , そのタスクにスイッチ するのて、す。 List 1 1 : / / TASK. HPP Copyright 1989 by Dlugosz Software 2 : / / マルチタスク用クラス集 3 : 4 : class task,• / / 前方参照 5 : 6 : cl ass task-head { / / 複数のタスクをリスト化する 7 : task_head* prev; 8 : / / 2 方向リンクトリスト task head* next; 9 : public: friend class task_iterator; void add_to-head (task * ) : void add-to-end (task * ) : 12 : 13 : void unlink(); / / リストから削除する 14 : void zero(): / / 空に初期化する 15 : task* fetch and_remove ( ) : 18 : class task_l ist ・ public task_head { / / タスクのリストを管理するためのコンストラクタと / / デストラクタを加える 20 : 21 : void unlink() { } 22 : public: 23 : task-list() { zero() ・ 24 : task-l ist() : 25 : b001 resume-one() : 26 : 28 : class task-iterator { //task_l ist をスキャンする 29 : task_l ist* start; 30 : task_head* current; 31 : inline task* result(); 32 : pub 1 i c : 33 : task-iterator (task_list& l) {current= start= & に } 34 : task-iterator() { } 35 : task* next ( ) : / / リストが終わったら next ( ) と pre v ( ) は N U ししを返す 36 : task* prev ( ) : task* same ( ) : / / 移動せずに同じものを再度フェッチする 38 : 39 : 40 : 41 : typedef void (*starter function) (void * ) : 42 : 43 : public task-head { class task friend void scheduler() : 44 : friend void fall-through(); 45 : 46 : friend void preemptable (b001) : void far* stack; 〃他の値はすべてスタック上に 48 : int priority_level; enum {preemptive=l, ki Ⅱ ab ニ 2 , ki 1 led=4, blocked=8, 49 : fauIted=16 } flags; 50 : public: vo i d k i Ⅱ ( ) : 51 : 52 : b001 iskilled() { return flags&killed; } 53 : bO 引 isblocked() { return flags&blocked; } 54 : b001 isfaulted() { return flags&faulted: } 55 : task (starter function f, int priority, void* stack, 56 : int stacksize, = NU しい : VOid* arg task() { kill() 〃直接には使わず、システム中の他のクラスが使用する 58 : 59 : void block (task_list&); 60 : void unblock ( b001 error= FALSE) : void add_to_list (task_l ist& l) { I. add_to_end(this) ・ 62 : 63 : 64 : 65 : extern void task_yield() : 66 : extern task* active_task; 67 : 68 : 69 : 70 : / / マルチタスク・モードに入るためにコールする 71 : extern void scheduler() : 74 : / / ューザコード中でこれらの関数をコールしてはいけない 75 : / / 以下のクラスにインラインコードを生成させた場合、 76 : / / 名前がグローバルなスコープを持っことを防ぐ方法がない。 / / リストの頭に入れる 〃リストの末尾に入れる タスク タスクは C 十十の中て、 task クラスとして抽 象化します。グローバル変数 current task が , 現在のタスクをポイントします。コン ストラクタが新たなタスクを作り , ディス トラクタがタスクを破壊します。タスクを 扱うそのほかの関数は , task のメンバにな ります。 タスクの構造を詳しくみてみましよう。 最初に , task head というクラスを定義し ています。これはふたつのポインタと , のような構造体をリンクトリストに挿入 / 削 除するメソッドだけのクラスて、す。 task ク この task head クラスから派生し ラスは , ます。 そのほかのいくつかのクラスも , task head と関連しています。 TASK. HPP(List2) に , オプジェクトの使い方をコンパイラに 指示するための C 十十のテクニックのいくつ 12 CMAGAZINE 19 3
C 十十 プログラミング 入門 静的て、あるか動的て、あるかだけ ) , 確保された領域の 作に相当するメンバ関数を作成します。考えられる 大きさ ( 要素数 ) を表すメンバ size が追加されたことてす。 操作には , ほかのスタックから初期化を行うコンス push 操作 , pop 操作の手続き Stack : : push トラクタ , 代入 , 追加などの操作があります。これ (TYPE * arg), Stack : : pop ( void ) は試作版とほ らを List6 に示します。これらは効率の悪さを無視す とんど同じて、す。しかし , スタック領域の確保の方 れば , push 操作 , pop 操作て、代用て、きるものて、す。 法が変わったため , コンストラクタは変更され , そ ロラ明クラス ( Gene 「 ic Class ) 乍成 してディストラクタも新たに必要になります。 それらを List5 に示します。新しい版のコンストラ 今度はさまざまな型を扱うことのて、きるクラスを クタ Stack : : Stack(uIong size) は , 引数 size て、受 考えてみましよう。 こまて、に示したクラス stack, け取った大きさの領域を演算子 new て、動的に確保し , Stack はあらかじめ決めた特定 ( specific ) の型 ( たとえ ポインタ s がその領域をさすようにします。また , イ ば int) に対してのみ働きます。そのため , 扱うデータ ンデックス P の値を 0 に初期化し , 領域の大きさを示 型が変わるたびに , その型用のクラススタックを定 す size に確保した大きさを設定します。ただしこ ' 義し直す必要があります。それは , しばしば好んて て、は , 引数もメンバもともに名前が size て、あるため , 使われますが , めんどうて、 , またあまり望ましい方 スコープ演算子により , これらを区別しています ( コ 法とはいえません。将来 , 導入されるて、あろう「補助 ラム 1 参照 ) 。また , ディストラクタ Stack : : St クラス (Parameterized CIass) 」の機能て、は , この問 ack(void) て、は , 演算子 delete によるスタック領域の 題はある程度解決されるて、しようが ( コラム 2 参照 ) , 開放を行います。扱う情報の型が組み込みの型て、は 現状の処理系 ( releasel. 2 あるいは 2.0 ) て、はまだ扱う ないかもしれませんのて、 , 演算子 delete は要素数を ことがて、きません。 引数にもっています。 そのため , いくつかの処理系て、は , 各データ型ご こまて、 , 領域の確保についていくつかの変更を とにクラスを定義し直す手間を軽減するための方法 行いましたが , 先の試作版のところて、示した List4 は を提供しています。 GNU C 十十ライプラリて、は , 何の変更もなく動作します。これはデータ構造をク genclass プログラムによる方法があります ( コラム 3 参 ラス内部に隠蔽した利点のひとって、す。 照 ) 。また , AT&T 系のライプラリて、は , マクロを使 次にいくっかの操作を追加することにします。本 った方法があります。これは , Stroustrup の TThe C 十十 Programming Language 』 [ 1 ] て、も紹介されて 来 , スタックには push 操作 , pop 操作以外の操作は必 要ありませんが , ここては説明のためにそれらの操 います。ここて、は , 後者の方法に従って , クラスス Class Stack の残りの手続き inline Stack: :stack (Stack& a) { = new TYPE[size = a. size]; S memcpy(), a. s, int(size * sizeof(TYPE))); List 6 (Stack& a) { inline Stack& Stack : :operator i f ()h i s = (a) { return *this; } delete s; = new TYPE[size a. S i. ze] : S memcpy(), a. s, int(size * sizeof(TYPE))) : return *this; inl ine Stack& Stack : :operator 十 = (Stack& a) { memcpy (&s[p] , &a. s[0] , int(). p * sizeof(TYPE))) : return *this; 0 1 ′ー」 0 00 86 CMAGAZINE 19N 3
C 十十プログラミング入門 小山裕司・正畠克俊 0 クラスの設計と実装 ( 1 ) 抽象テータ型について簡単に解説しその後で , 基本的なテータ構造スタ ました。今月号から , いよいよクラスの設計についての話です。今回は , げます。その導入として , 1 月号 , 2 月号では C 十十の讎吾仕様を紹介し 本連載では , クラスの設計を中心に , プログラミングの話題を取り上 ックを抽象テータ型として実装する例を示します。 ロ抽象データ型 1 , 2 月号て、 , クラス ( Class ) が抽象データ型を実装 するために利用されることについて説明しました。 こて、は , その抽象データ型について , もう少し具 体的にお話したいと思います。 ロ情報の表現 通常 , コンピュータは情報をビット ( 0 と l) の列と して取り扱います。これは機械語て、プログラムを組 んだことのある人て、あれば , よくご存じだと思いま す。たとえば , 数学的な意味の一 365 という数値は , あるコンピュータて、は Fig. 1 (b) のように ほかのコ ンヒ。ュータて、は Fig. 1 ( c ) のように内部表現されます また , メモリへの格納形式も Fig. 2 のように何 種類かあります ( 注 2 ) 。 これらの情報の表現はコンヒ。ュータの演算装置や 言語処理系の設計および実装に依存します。そして , Fig. 1 負数の表現 (a) ( d ) ー 365 ( c ) ー 365 ( b ) ー 365 365 0000 0001 1 1 10 1 1 10 1000 0001 01 10 1 001 1001 01 10 A D 1 101 001 1 0010 1 101 Fig. 2 メモリへの格納形式 0 VAX 68000 A D B C C B ビットの列て、表現された情報はわすかに用意された 作は , 非常に低レベルなものなのて、 , 多くの場合 , す。その例を List1 に示します。ただし , それらの操 ビット演算操作 , 算術演算操作によって処理されま Assembly Language(Sun SPARC ) のプロクラム例 . g I oba 1 ma i n List 1 注 1 負数の表現方法には いくつかある。 Fig. 1 ( b ) は 2 の補数を , Fig. 1 ( c ) は 1 の補 数を使う方法て、 , ほかに符 号ビットと絶対値を使う方 法 (Fig. l(d)) などがある。 注 2 この異なるコンヒ。ュー タ間の問題は byteswap と呼 ばれる。 82 CMAGAZINE 19 3 ma ー n : CONST: . proc 1 save Xsp, ー 128 , %sp mov 5 , % 00 st % 00 , [%fp-20] ld [Xfp-20] , %f2 fitod Xf2,%f2 sethi %hi (CONST) , %gl ldd [%gl + XIo(CONST)],%f4 fmuld Xf2,%f4,Xf2 std %f2, [Xfp-32] ret restore . align 8 . double 0r2. 54000000000000003553e + 00