コンストラクタ - みる会図書館


検索対象: 月刊 C MAGAZINE 1990年4月号
12件見つかりました。

1. 月刊 C MAGAZINE 1990年4月号

List 3 C 十十 プログラミング 入門 intgstack::intgstack(ulong size = 10 の { cout くく ' ' スコープの始まり ( 大きさ " くく s i ze くく " のスタックの生成 ) Yn" : new int [size] : S intgstack: : size 2 : 3 : 4 : 5 : 7 : 8 : intgstack: intgstack (void) { 9 : delete[size] s; cout くく " スコープの終り ( スタックの破壊 ) Yn" ・ S 1 ze ; intgstack spI(IOOO); になります。 これはコンストラクタの引数がひとつのとき ( これから生成します こか スコープの始まり ( 大きさ 100 のスタックの生成 ) intgstack spI 1000 ; スコープの始まり ( 大きさ 100 のスタックの生成 ) という初期化とまったく同じて、す。 この処理はたん こはスコープの内側 なる初期化て、 , コンストラクタは一度だけ呼び出さ スコープの終わり ( スタックの破棄 ) れます。 こて、は , 後述する代入によるインスタン スコープの終わり ( スタックの破棄 ) スの置き換えはされません。 もう破棄されました コンストラクタは領域を確保した直後に呼び出さ ■動的なインスタンス れ , ディストラクタは領域を解放する直前に呼び出 さて , 話を戻しますが , このようなインスタンス されます。なおここて、は , ふたつの要素を生成して の生成および破棄時の処理は , それが記憶クラスが いるため , コンストラクタおよびディストラクタは 静的て、あっても , 動的て、あっても基本的に同じて、す。 2 回ずっ呼び出されています。 それが静的て、あれば , プログラム起動時にコンスト 0 テータのメンバの初期化 注 3 静的なインスタンスの ラクタが , 終了時にディストラク . タが呼び出されま 生成および破棄時の処理の 実現方法については , 本誌 す ( 注 3 , 4 ) 。動的なインスタンスは , 演算子 new により コンストラクタはインスタンスの生成を行います ( ' 90 年 1 月号 ) に掲載された 生成され , delete により破棄されます。次にその例 「アセンプラレベルから見た が , その主要な仕事は , おそらくデータのメンバの C 十十プログラミング活用」 を示しましよう。 初期化て、しよう。その方法にはいくっかあり , List1 に詳しく説明されている。 cout くく ' これから生成します¥心 て、示したコンストラクタもそのひとって、す。しかし , 注 4 いくつかの処理系は , intgstack * pst new intgstack [ 2 ] ・ これは本当に正しい ( 優れた ) 方法て、はありません。 この静的なインスタンスに 関する処理がいい加減てあ cout くく こはスコープの内側 \ 心 ; , こて、 , 新しいコンストラクタを再度定義すること ったため , プログラムに細 delete [ 2 ] pst; 工しないと正常に動作させ にします (List4 参照 )。 ることがてきなかった。 cout くく ' もう破棄されました \ 心 ; 新しいコンストラクタて、は , データのメンバの初 , こて、は , スタックは動的に生成 ( メモリ空間に配 期化はコンストラクタ内部て指定されるのて、はなく , 注 5 コンパイル時てはな く , プログラム実行時に必 置 ) され , 必要なくなった場所て、破棄 ( その領域は解 関数の仮引数の宣言に続く初期設定リストて指定さ 要な大きさが決定される領 放 ) されています。演算子 new は , 指定した要素数だ 域は , このヒープ領域に確 れています。この部分には , データの各メンバにつ 保される。これらの管理機 けの領域をヒープ領域 ( 注 5 ) に確保し , そこへのポイン いて , それぞれメンバ名およびその初期値リストが 構は , OS が提供する固定領 域をリスト構造などて、扱う タを返します。一方 , 演算子 delete は , 領域のポイ 書かれます。これらは , データのメンバのコンスト ことにより , 適切な領域を ンタを受け取り , その領域をヒープ領域に解放しま 使用者に与えることがて、き ラクタを呼び出すのに使われます。この場合 , 引数 る。なお , さらに詳しく知 す。これらの処理は , C 言語の標準ライプラリ関数 size をもって , メンノヾ size のコンストラクタが呼び りたい方は「プログラミン グ言語 C 」などをご覧になる malloc および free に似ています。しかしこれらの演 出され , 同時に引数 0 をもってメンバ p のコンストラ とよいだろう。 算子は , それらの関数の機能に加えて , 確保時には クタが呼び出されます。 注 6 演算子 new および 各要素ごとにコンストラクタを呼び出し , 解放時に 新しいコンストラクタと古いコンストラクタとの delete については本連載第 は各要素ごとにディストラクタを呼び出します ( 注 6 ) 。 2 回 ( ' 90 年 2 月号 ) のコラムを 違いはプログラムの表面には現れないて、しようが , 参照のこと。 したがって , 先に示した例の実行結果は以下のよう 動作においては異なる場合がしばしばあります。不 102 CMAGAZINE 19 4

2. 月刊 C MAGAZINE 1990年4月号

List 2 1 : v 0 i d ma i n ()O i d) くく n ” 2 : 3 : 4 : 6 : 7 : intgstack st; st. push(100); st. push(75) : cout くく st. pop() intgstack のコンストラクタとディストラクタを List3 のように修正します。それぞれ , 自分が呼び出され たときに , それを私たちに知らせるためにメッセー cout くく”スコ intgstack sp; cout くく”スコ 以下の処理 ジを出力します。 ープの内側¥ n ープの外側 \ 心 ; くく ' スコープの外側¥ n ' ; に対して , 次のような実行結果が得られるて、しよう。 スコープの外側 スコープの始まり ( 大きさ 100 のスタックの生成 ) スコープの内側 スコープの終わり ( スタックの破棄 ) スコープの外側 cout それは各インスタンス ( この場合 sp ) について , ス す。その場合 , 以下のようにして初期化てきます。 すことて、 , 扱う要素の大きさを決めることがてきま クラス intgstack は , コンストラクタに初期値をわた び出しは , 初期値をもっていません。しかし , この つまりコンストラクタの呼 のインスタンスの宣言 , ストラクタが呼び出されます。余談てすが , ここて プの最後 ( この場合 , そのプロックの終わり ) にディ コープの最初 ( 宣言時 ) にコンストラクタが , スコー メ、バへのポイタ 複雑なプログラムでは , しばしばポインタを使っ てメンバを取り扱ったほうがよいことがある。 , , では , その方法を説明する。あるクラス TYPE のメ ンバへのポインタは , 修飾子 TYPE : : * によって指定 され , 逆参照演算子 . * およびー > * によってアクセ スされる。その例を示すために , 以下に簡単なクラ スを定義する。 class F00 { public: Char gomi; VOid nanka(char); このクラスのメンバへのポインタ dmp は以下のよ うに宣言でき , 各クラスのメンバはアクセスされる。 char 00 : : * dmp = & F00 : :gomi; F00 a, * b = new FOO; a. * dmp; int fa int fb = b ー > * dmp; データのメンバ gomi はポインタ dmp によって指定 され , 逆参照演算子を使って参照され , 変数 fa およ び fb に代入される。 関数のメンバへのポインタについても同様である が , 関数へのポインタの指定はいつもどおりめんど うである。そのため , ふつう typedef を使って型に別 名をつける。次にその例を示す。 typedef void(FOO: : * fmpt) (char); このポインタ fmpt を使えば , メンバ関数は以下の ようにしてアクセスできる。 fmpt fmp = & F00 : :nanka; F00 a, * b = new 00 ; それぞれ , インスタンス a および b の F00 : : nanka 関数を引数 ' a ' あるいは ' b ' をもって呼び出す。 こで紹介したメンバへのポインタの機 しかし , 能は , 残念なことに AT & T の Re 厄 ase2.0 以降の機能で ある。そのため , それ以前の処理系では CStr86] に 紹介されている暫定的なごまかしを使わなければな らない。このごまかしを使う場合は , メンバ関数へ のポインタは以下のように typedef で別名をつける。 typedef void ( * fmpt)(void * , char); そして , このポインタは以下のようにして使うこ とができる。 F00 a, *b = new F00 ; fmpt(&a. nanka); fmpt fmp この方法は , 実際にはメンバ関数が隠れた第 1 引数 の this をもっことを利用している。それは , C 十十の コードから得た C 言語のコードを見れば明らかである が , 型 TYPE のメンバ関数 TYPE: :foo(arg, は以下の TYPEfoo(TYPE * this, arg, と同じであり , C 十十処理系が C 十十から C 言語へのト ランスレータで実現されていることを利用している ( ただし , 名前 TYPEfoo には , 処理系によって型名 TYPE および関数 f00 を組み合わせた適当でユニーク な名前が選ばれる ) 。そのためトランスレータでない C 十十ではこの方法が使えるかどうかの保証はない。 C 十十プログラミング入門 101

3. 月刊 C MAGAZINE 1990年4月号

List 4 : p(0), size(size) { = 10 の intgstack: : intgstack(ulong size new int[size] ; S , 1 っ 0 っ 0 化もしなければなりません。 List5 (a) のコンストラク 運にもこのクラス intgstack は , 多くの処理系の実装 タは , 初期設定リストを使用してデータのメンバを て、は , 動作においても違いが現れないため , 違いを 示すため , 新たなクラス vm を作成します ( 注 7 ) 。 List5 初期化します。しかしながら , List5 (b) のものは , そ の内部の代入を使っており , その動作は ( a ) とは明ら にクラス v 用の要約版および 2 種類のコンストラクタを かに異なります。 示します。 クラス v 爪のメンノヾて、ある st は , クラス intgstack このクラス vm は , データのメンバとしてスタック のインスタンスて、あり , それは生成されるたびに をもち , そのコンストラクタはそのスタックの初期 必ず初期化されます ( 注 8 ) 。そのため ( b ) の方法て、は , st は一度デフォルトのコンストラクタにより生成され , 再度内部の代入時に , その左辺項を初期値として生 成されたインスタンスに置き換えられます。つまり , メンバ st に対してコンストラクタは二度呼び出され ます ( それぞれの動作を Fig. 1 に示します ) 。ューザ は , この初期化と代入を混同しないように注意しな ければなりません。 ーメンノヾ関数と フレンド関数 次に演算子の役割をする演算子関数を中心にメン バ関数およびフレンド関数について解説します。 これらの機能については , すてに説明しましたが , List 5 1 : class vm { 2 : private: / / data member 3 : intgstack st; 4 : 5 : public: vm(int size) 6 : constructor vm(void) { } : / / destroctor 7 : 8 : 1 1 : / / (a) w i th i n i t i ⅱ zat i on 1 i st. 12 : vm : : vm ( i nt s i (e) : st (s i (e) { 16 : / / (b) without initilization list. 17 : vm : : vm ( i nt s i (e) { st S ー ze : Fig. 1 初期化におけるふたつのコンストラクタの性質の違い (a) 320 最初から要素 500 のスタックが成生される (b) 1 ます要素 100 のスタックを成生する 注 1 クラス vm は , ある分散 システムの仮想スタック機 械を実現するために作成し たものてある。ただし , 現 ノヾージョンは , SPARCsta tion 上てしか動かすことカイ きず , プログラムも複雑な のて , ここては要約版のみ を示す。 注 8 クラス intgstack は , 初期値なし , つまり引数な して呼び出せるコンストラ クタをもっため , ・それを使 って初期化される。 3208 次に要素 500 のスタックを成生する 1 側 32 その 500 のスタックを新しい SP として 置き換える 1 OO C 十 + プログラミング入門 103

4. 月刊 C MAGAZINE 1990年4月号

Trace オプジェクトにはローカル格納を含 んて、いない。 Trace クラスのデータメンノヾ はスタティックだけて、ある ( これはクラスの すべてのインスタンスにより共用されるた めて、ある ) 。そのうち , データメンバ level は 現在のインデントレベルを保持するものて、 1 : A>set DEBUGMODE=15 ある。もう一方の traceM0de はトレース出 カ許可ビットを保持するものて、ある。両者 ともクラスのプライベートメンバて、ある。 クラスはふたつのコンストラクタをもっ ている。ひとつめのコンストラクタは , ク ラスを初期化するのに用いるものて、ある。 A> t e s t : ma ー n i n { 0 : i n f 0 : i n f 0 : t e s t. g a 「 b a g e Th e a d d 「 e s s 0 f Th e a d d 「 e s s 0 { Th e a d d 「 e s s 0 { CPP(44) : a S S e 「 t t 「 a c e i s 2A62 c i s 2A 6 2 0 p t i nd i s 0 p t i れ d 1 8 6 「 g C 06 を用いて { a i le d ヨ 0 0 pC h a 「 e n t e 「一 n g ー 0 0 p Th i s i s a t e s t Th i s i s a t e s t e x i t i n g ー 0 0 p G 0 0 d ト y e ( 訳注 . PCー9801VM2 で Zortech C 十十 コンパイルした実行例 ) LiSt trace. h verl . 2 : 3 : 4 : 5 : 6 : 8 : 9 : 10 : 18 : 20 : 22 : 23 : 25 : 26 : 28 : 29 : 30 : #include く stdio. h> #include く stdarg. h> enum TraceMode { error, infO c lass Trace { public: Trace() : Trace(const Char *name) : Trace(void) : Trace &operator() (const char *str) : Trace &operator() (TraceM0de traceMode, void doAssert(const char *file, int ド Trace &setTraceMode(unsigned int trace ode) : private: void indent(void); int assertSet(void) : int traceSet(void) : int errorSet(void) : inf0Set(void) : int ne, const Char *msg) : const char *fmt, static unsigned short level; / / インデント ( 字下げ ) static unsigned int traceMode; / / 出力許可ビット #define assert(trace. condition) { \ if (!(condition)) { \ レベル これは , 単一のスタティックな Trace のイ ンスタンスを , 文字列引数なしの Trace コ ンストラクタを使用して , Trace をインプ リメントするファイル内て、定義することて、 実現する。スタティックなインスタンスて、 あるのて、 , コンパイラは , main( ) を開始さ せる前にコンストラクタが呼び出されるよ うなコードを生成する。コンストラクタは , 環境変数から D 刊 BUGMODE を探して , ス タティックメンバ traceM0de を初期化す る。これは main( ) が開始する前に動作する のて、 , トレース出力は main ( ) が開始する前 に許可される。しかし , たとえ許可されて も , Trace 初期化のコンストラクタの前に コンパイラの開始コードに呼ばれるコンス トラクタは , トレース出力はもっていない て、あろう。もうひとつのコンストラクタは , Trace オプジェクトが前述のように定義さ れるとき呼ばれる , 通常のコンストラクタ て、ある。 関数呼び出しオペレータは 2 種類のものが 定義されている。ひとつは , パラメータと して一定の文字列をとり , トレースメッセ ージ出力をインプリメントするために用い られるものて、ある。もうひとつは , 第 1 パラ メータを列挙型 TraceMode として以下 , 出 カフォーマットを記述する文字列や , その 文字列の内容に従う可変個数のパラメータ という形をとるものて、ある。この関数の条 項て、ある Requires のなかて、詳細に示されて いるように , 呼び出す側は , 出力フォーマ ットを記述する文字列とともに , 正しい数 と型のパラメータを渡すことを保証しなけ ればならない doAssert オペレーションは assert マクロ によって呼び出される。 setTraceMode は , 環境変数 DEBUGMODE からセットさ れたデフォルト値の traceM0de を無効にす るために用いられる。 List2 にもこの使用例 が含まれている。 いくっかのプライベート操作が記述され C 十十のデバッギングクラス 39

5. 月刊 C MAGAZINE 1990年4月号

List 1 C 十十 プログラミング 入門 1 : class intgstack { 2 : private: 3 : int* s; 4 : u long p : 5 : u ー ong S i ze : 6 : P ub ⅱ c : i ntgstack (u long 7 : = 10 の : 8 : intgstack(intgstack&) : 9 : intgstack(void) : void push(int); int pop(void) : (intgstack&) ; intgstack& operator intgstack& operator 十 = (intgstack&) : int operator ロ (ulong) : 14 : 17 : inline intgstack: : intgstack(ulong size = 10 の { new int[size] : S 20 : intgstack: :size 21 : } 22 : 23 : inl ine intgstack: : intgstack(intgstack& a) { 24 : P = a ・ P : new int[size a. S i ze] : 25 : S memcpy(), a. s, int(size * sizeof(int))); 26 : 28 : 29 : inline intgstack: delete[size] s; 30 : 32 : 33 : inline void intgstack::push(int arg) { if (p > = size) { cerr くく 34 : " intgstack overflow Yn ” : } 35 : e I se arg : P 十 = 1 36 : } 38 : inline int intgstack::pop(void) { if (p く = の { cerr くく 39 : " intgstack underflow Yn"; 40 : 1 : return s[p] ・ e 1 se 41 : } 42 : 43 : inl ine intgstack& intgstack: :operator if (this = (a) { return *this; } 44 : delete s; new int[size a. S i ze] : S memcpy(), a. s, int(size * sizeof(int))); 49 : return *this; 50 : } 52 : inline intgstack& intgstack: :operator 十 = (intgstack& a) { 53 : memcpy(&s[p], &a. s[0] , int(). p* sizeof(int))); 54 : 55 : return *this,• 58 : inline int intgstack::operator ロ (ulong (t) { if ()t > = p) { return ー 1 : } 60 : { return s [pt] ・ e I se S ー ze : intgstack(void) { (intgstack& a) { 注 2 ここては , あるクラス に属するように宣言された オプジェクトをインスタン スと呼んている。また , くつかの文献ては , その手 続き ( メソッド ) を共有し , 情報 ( データ ) のみをもっこ とを強調するためにこの単 語を使っていることもある。 詳しい解説とその適切な使い方を示すことにします。 コンストラクタ , ーティストラクタ ■クラスのインスタンスのおよ棄 前にも述べましたが , コンストラクタがインスタ ンス ( 注 2 ) の生成を , ディストラクタがインスタンスの コンストラクタおよびディストラクタについては , 本連載の第 1 回に説明しましたが , ここて , もう少し 破棄をします。その動作を確認するために , クラス 100 CMAGAZINE 19 4

6. 月刊 C MAGAZINE 1990年4月号

み合わせて、あれば , 暗黙の型変換が自動的に行われ ます。しかし , 可能な変換手順が複数ある場合 , ど の手順を使うかを明示的に指示する必要があります。 次にコンストラクタの逆の働きをする , あるクラ スのインスタンスからほかの型への変換を考えます。 これは演算子関数のひとっとして定義されます。 List7 に intgstack を int の配列に変換する型変換の演算子 関数を示します。このように演算子関数は , 変換後 の型名が TYPE ( この場合 int * ) て、あれば , 関数名 operator TYPE をもちます。 ■演算子関数 演算子関数は , 関数名のつけ方が少し変わってい るほかは , 通常の関数と同じて、す。それらの演算子 そのときに具体的な例をあまり取り上げていなかっ 関数は言語組み込みの型の演算子と同様に中置表記 たのて , 今回 , いくつかの定義例を示しながら , そ が可能て、あり , 新しい型を C 十十既存の型体系に混ぜ の用途の説明をします。 合わせることがて、きます。ここて、 ,intgstack インス タンスの加算のための関数の例を考えましよう。普 ■算子 通の関数として定義する関数 add は List8 のようにな さて , コンストラクタの働きはインスタンスの生 り , それは以下のようにして呼び出されます ( 注 9 ) 。 成てした。しかし , それは型変換というもうひとつ の役割をもちます。それは , 代入あるいは関数呼び 演算子関数は , 演算子@の働きに相当する関数を 出しのときに使われます。以下に int の配列を intg 関数名 operator@て、定義することによって用意され stack 変数に代入するときの型変換の例を示します ( is ます。関数 add と同機能の演算子関数 operator + を は intgstak 変数 ,ia は int の配列へのポインタとしま List9 に示します。これを利用すれば , 先ほどの式は 以下のような構文て、書くことがて、きます ( 注 10 ) is intgstack(ia); a = b 十 c ・ なお , この int 配列からのコンストラクタは List6 に このように 組み込みの型同様に , ューザが ー「 4 ロロ 示します。 定義した抽象データ型を被演算数 ( オペランド ) にも こては関数呼び出し形式の型変換を使っていま った演算子を中置表記の構文て、書けることは , たい すが , C 言語から譲り受けたキャスト形式も使うこと へん便利て、すが , いくっかの制限があります。しか がて、きます。複数の初期値をもった型変換には , 前 し演算子関数は , C の型体系を基本的に引き継いて、い 者の関数呼び出し形式を , ポインタなどを含んだ複 るため , それらは組み込みの演算子と同じ被演算数 雑な型への型変換には , 後者の型変換を使う必要が をもち , 同じ優先順位をもち , そして同じ結合規則 あります。 をもちます。それらを変更することも , 新たに演算 この例て、は , 明示的な変換の指定を行いました。 子を定義することもて、きません。たとえば , 指数演 変換前から変換後の型への変換手順が , 言語組み込 算のために , その機能を記号 * * にオーバーロード みの型変換ひとっとユーザ定義の型変換ひとつの組 することはて、きませんし , 記号・にオーバーロードす C 十十 プログラミング 入門 List 6 intgstack: : intgstack(int* (i) { while (pi[p + + ] ! = new int[size = P * 2 ] : S int(p * sizeof(int))); memcpy(), pi, ・ 1 り 0 っ 0 -4 -0 6 List 7 intgstack: :operator int* (void) { = new int[p] : int* pi memcpy(pi, s, int(p * sizeof(int))); return PI : 、 1 っ 0 っ 0 -4 L.n 注 9 ここての関数 add およ び operator 十は , クラス intgstack のフレンドてなけ ればならない。 注 10 演算子関数は , 無理 に中置表記構文て書く必要 はない。以下のような関数 呼び出してもかまわない。 operator 十 (), c); List 8 intgstack& add(intgstack& a, intgstack& b) { new intgstack(). size 十 b. size) : intgstack* ps return *ps; 1 り 0 っ 0 ・ 4 0 6 a 104 CMAGAZINE 19 4

7. 月刊 C MAGAZINE 1990年4月号

単純な配列 List4 に , 文字列て、インデクシングされる整数の配 列 count arrays と , それを使って標準入力ストリー ムからの語彙数を数える単純なプログラムを示して おきます。この配列の成分て、ある count nodes クラ スは , べースクラス bst nodes から派生しています。 新たに加わったものは , 整数値 count と , count を初 期化しインデクスの値 ( 文字列ポインタ ) をベースク ラスのコンストラクタに渡す新たなコンストラクタ , および BST のひとつのノードをストリームに出力す freq. cpp List 4 12 : } : 25 : { 28 : } 32 : { 37 : } 47 : } count-arrays() {tree = NU しい} 15 : public: count_nodes *tree; class count_arrays { friend class count_arrays : friend void print(bst_nodes * , ostream & ) : (s) {count count-nodes(char *s) int count : class count_nodes bst_nodes { 6 : / / 文字列でインデクシングする count の配列 # include ” bst. hpp" #include く stream. hpp> 1 : / / freq. cpp る private な関数 print( ) て、す。 クラス count arrays には , count nodes の BST へのポインタと , コンストラクタおよびデストラク タ , さらにアクセス関数て、あるロ演算子 ( をオーバ ロードしたもの ) と , 出力ストリーム関数て、ある演算 子 ( をオーバロードしたもの ) があります。ユーザ定 義型の出力ストリーム関数は , ostream のメンバ関 数て、ある必要はありません。普通は、そういう関数は クラスの friend 関数にして ,private なメンノヾのすべ てからアクセスて、きるようにするのがベストて、す。 main( ) の中の cin word という式が , スペースて、区 切られた、、語彙〃を標準入力ストリームから読んて、 , 2 : 3 : 4 : 5 : 7 : 9 : 10 : 11 : 13 : 19 : 20 : 22 : 24 : 26 : 34 : 40 : 42 : 43 : 44 : void print(bst_nodes *t, ostream &stream) 23 : / / print ー count-nodes を出力する {s. tree->bst-nodes: :foral 1 ()r int, stream) : } friend ostream &operator くく (ostream &stream, count_arrays s) int &operator[] (char * ) : count-arrays() {if (tree ! = NUL し ) delete tree : } stream くく s—>string くく "Yt ”くく s->count くく” Yn"; register count_nodes *S (count-nodes * ) t; i nt ma i n ( ) 39 : / / 語彙出現頻度のカウンタ return t->count; new count nodes(s) : = NU しい register count_nodes *&t (count-nodes * ) *lookup(&tree, int &count_arrays: :operator ロ (char *s) 30 : / / operator ローロ演算子のオーバロードによるアクセス関数 cout くく word_table; + + word_table[word] : while (cin > > word) count_arrays word_table; char word [ 80 ] : String-Indexed A 汁 0Y5 in ( 十十 「文字列でインデクスする配列」を C + + で実現 31

8. 月刊 C MAGAZINE 1990年4月号

List 3 2 : 3 : 7 : 9 : 14 : 20 : 22 : 24 : 26 : 28 : 29 : 35 : 36 : 39 : 30 CMAGAZINE 19 4 3 、 ndexed A 「「 0Y5 30 : } 34 : { 40 : } •n ( 十十 など一部の言語には , すでに取り入れられています。 C で ListI-a のように書くところを , C 十十では List1 -b のように書けます。そしてこれが可能であるな ら , 配列をインデクシングするのに , 浮動小数点数 , ューザが定義したデータ型 , 不連続的な整数値域な ど , 何でも使えることになります。配列をどんな方 法で実現しようとも , その実現方法の細部は隠蔽さ れ , それが表から見て配列であることは変わりませ ん。本稿では , 文字列でインデクシングする配列を 二分探索木 (Binary Search Tree [BST] ) とハッシ ュテープルで実現する方法を紹介します。 List2 と List3 で , べースクラス bst nodes を定義し ています。 BST の各ノードにはキーとなる値と , ふ たつの子木 (subtree, サブツリー ) へのポインタがあ bst_nodes: : bst_nodes() 13 : / / デストラクタ strcpy(string = new char[strlen(s) + 1 ] , left = right ニ NU しし : bst-nodes: :bst-nodes (char *s) 6 : / / コンストラクタ 4 : #include "bst. hpp ” #include く stream. hpp> 1 : / / bst. cpp bst. cpp ります。文字列でインデクシングする配列の場合 , キーはインデクス用文字列へのポインタです。今回 のインプリメンテーションでは , ひとつのノードの 左の子木はすべてそのノードより小さく , 右の子木 はすべてそのノードより大きいという慣例に従って います。 コンストラクタ bst nodes( ) は単純明快で , ふた つの子木を空にセットし , インデクス文字列用のス ペースをアロケートし , そのスペースに文字列をコ ピーします。 デストラクタ -bst nodes( ) は , 左の子木 , インデ クス文字列および右の子木を再帰的に削除します ( 削 除していく順序は重要ではありません ) 。 BST を探索 するプリミテイプ lookup ( ) と , ツリー全体を遍歴す るためのプリミテイプ forall ( ) が , このべースクラ スをしめくくっています。 i f ()h i s ! = NU しい { delete left; delete string, delete right; 23 : / / lookup ーツリー探索プリミテイプ bst-nodes **lookup(register bst nodes **t, char *index) int cc; while ()t ! = NU しし & & ()c = strcmp(index, (*t)->string)) ! = の return t; 32 : / / forall ーツリー遍歴 33 : void bst-nodes: :forall (void (*ftn) (bst-nodes * , ostream & ) , ostream &s) i f ()h i s ! = NUL い { left->forall(ftn, s); (*ftn) (this, s) : right->forall (ftn, s) :

9. 月刊 C MAGAZINE 1990年4月号

, 0 。つラスの設計と実装 ( 2 ) C 十十プログラミング入門 小山裕司・正畠克俊 前回は簡単なテータ構造であるスタックを例に , 試作クラスか ら汎用クラスに至るクラスの設計および実装の過程を解説しま した。今回は , その続きとして , インタフェイスの設計と実装 を中心にお話したいと思います。また , 関連の深いメンバへの ポインタについてもコラムで取り上けました。 クラスの仕様に定義されていない機能は , 偶然そう クラスへの なっているだけかもしれないのて、 , 将来の実装て、は インタフェイスとは 変更される可能性があります。 C 十十のクラスは , 抽象データ型のデータ構造など ー前回の復習 の細かい実装を隠し , インタフェイスの関数からの アクセスのみを許す機構を提供します。つまり , 抽 象データ型の機能は , インタフェイスの仕様によっ 前回は抽象データ型の定義例を示すために , スタ てのみ決定されるものて、 , 実装によって左右される ックに相当するクラスを作成しました。今回も例題 べきものて、はありません。そして , 実装のつごうに としてスタッククラスを使うのて、 , スタックについ よって内部のデータ構造が変更されたとしても , イ て簡単に説明しておきます。スタッククラスの試作 ンタフェイスの仕様が変更されないかぎり , ューザ クラスから汎用クラスまて、の数種類の実装例を示し ました ( 注 1) 。ただし , これらの違いは , 実装方法の違 が記述したコードには影響を与えません。 このように抽象データ型をカプセル化することが い ( 扱うデータ型やスタック領域の種類の違い ) て、あ り , 今回扱うテーマにあまり関係ありません。 て、きれば , 大規摸なプログラムの開発において威力 て、はスタッククラスのあるノヾージョンのみを示しま を発揮するて、しよう。まず簡単な実装を行ってイン す (List1)0 このクラス intgstack は , 前回の汎用ク タフェイスを決定すれば , それを使ってユーザはプ ラス gstack から以下のようにして生成したものと等 ログラムの開発を始めることがて、きますし , 後て、実 価て、 , int 型の情報を扱います。 装を優れたものに変更しても , ユーザはたんにプロ #include "gstack. h" グラムをコンパイルし直すだけて、かまいません。 declare (gstack, int); これはあくまて、正しいインタフェイスをクラス設 implement (gstack, int); 計者が用意している場合て、す。そのため , 内部構造 への不正なアクセスを許したり , 実装に依存してい そして , このクラス intgstack は List2 のようにし るインタフェイスは望ましくありません。優れた抽 て使用されます。 こては , スタックの操作にメン 象データ型かどうかは , そのインタフェイスの仕様 バ関数 push , POP が明示的に使用されており ( 4 ~ 6 行 にかかっているといえるてしよう。 目 ) , 生成化および破棄にコンストラクタ ( 2 行目 ) , ディストラクタ ( 8 行目あたり ) が暗黙のうちに使用さ そして , クラスのインタフェイスは , クラスへの 実際の操作およびクラスの動作に相当するものがし れています。今回は , よりよいインタフェイスを設 計および実装するための知識として , クラスのイン ばしば用意されます。それは , 直感的なもののほう タフェイスの役割 , 動作を中心に話を進めていくつ が , ューザにとってよりわかりやすいからて、す。 ユーザも , クラスの設計者によって定義されてい もりてす。 る仕様のうえてクラスを使用しなければなりません。 注 1 前回 , 試作クラスの stack, ふつうのクラスの Stack, 汎用クラスの gstack を示した。このほか に , 補助データ型 (par ametarized type) や gen class の機能を使ったバージ ョンもコラムて示した。 C + + プログラミング入門 99

10. 月刊 C MAGAZINE 1990年4月号

i09 0055 薈 0 「 ( 十十 A Debug\: List 3 ている。 assertSet, traceSet, error Set, inf0Set はアクセス関数て、ある。オプ ジェクト指向プログラマにとって , アクセ ス関数を使用するかしないかという問題は , 宗教的な問題 (religious matter) て、ある。あ るものは信じて使用し (swear (y) , またあ るものはまったく同調しない (others (t) 。 この問題に対してプログラマはどのような 立場をとるかを決心しなければならない 私といえばアクセス関数を使用するほ うの立場だが , 読者のみなさんを同じ立場 に転向させようとまて、は思っていない assert マクロは Trace クラスのあとに定 義している。これは簡単なマクロて、あるが , パラメータ condition を文字列に変換する文 字列化オペレータ ( # ) を含んている。ただた んにマクロバラメータを引用符て、囲むとい う古い方法は , より新しいコンパイラて、は 動作しないものがあるかもしれない ヘッダファイルの残りは , クラスのイン プリメントの始めの部分て、ある。可能なか ぎりの関数をインライン関数として作成し たのて、 , デバッグコードは最小のオーバー ヘッドをもつだけて、すんだ。トレースのオ ーバーヘッドは , トレース出力が禁止され , コンノヾイラが inline 孑旨令に従っているとき , ほとんどのトレース呼び出しの AND 演算や 分岐命令からなる。 ファイル trace. cc(List4) には , スタート アッブオプジェクト initTraceMode の宣言 を含んて、いる。これは , どのプログラムて、 もこのモジュール以外て、は必要て、なく , 外 部シンポルテープルに存在している必要が ないのて、 , スタティックとして定義してい スタートアッブオプジェクトのクラスの コンストラクタは , operator( ) の 2 番目の こて、定義され バーション定義とともに る。以前にも述べたとおり , このバージョ ンは可変個数の引数をとるためインライン 関数として定義していない。したがって , #condition) : \ い NE trace. d0Assert(--FILE 32 : 33 : } 34 : 35 : static const int incr 37 : enum { 38 : 39 : 40 : errorMask 44 : 45 : i n ⅱ ne 46 : Trace: :Trace(const char *name) if (traceSet()) { indent() : fputs (name, stderr) : fputc ('Yn' stderr) : 52 : I eve ー十ニ i ncr : 56 : i n ⅱ ne 57 : Trace: : Trace (void) 58 : { 59 : lncr; 62 : inline Trace& 63 : Trace: :operator() (const char *str) i f (traceSet ( ) ) { indent() : 66 : fputs (str, stderr) : fputc ( ・ Yn' stderr) : 70 : return *this; 73 : i n 1 i ne vo i d 74 : Trace: :doAssert(const char *fi le, int line, const char *msg) if (assertSet() ) { indent() : fprintf(stderr, "Xs(Xd) : assert Y"XsY" failedYn" file, ー ine, msg) : 86 : inline void 87 : Trace: : indent (void) for (int i level; fputc ( ' stderr) : 94 : i n ⅱ ne i nt 95 : Trace : : assertSet ()o id) return traceMode & assertMask : assertMask = 0X01 , traceMask = 0X02. = 0X04 , infohask = 0X08 40 CMAGAZINE 19 4