void - みる会図書館


検索対象: 月刊 C MAGAZINE 2000年7月号
58件見つかりました。

1. 月刊 C MAGAZINE 2000年7月号

たとえば , 定時的に処理をするためのサー ながら C + + でも被害甚大であり , むしろ C 言 ビスを提供する抽象クラスとして LPeriodic [ 例外 ] 語以上に注意しないといけません。 メンバをたくさんそろえることで汎用的 al , オプジェクト同士の交信をするための なし。 な利用を増す , あるいは豪華な仕様である [ 備考 ] LBroadcaster/LListener など , いくつかの ことが売り物になる , という勘違いがあり 規模に関する被害は甚大であることは禁 軽いクラスから成り立ち , それらのクラス ます ( とくに商用クラスライプラリでよく じ手の C 言語編でも説明しましたが , 残念 には共通の基底クラスがありません ( した がって菱形継承の問題も起きません ) 。 複数のメンバ関数を操らせる うすることで継承レベルをきわめて浅くで # ー ude く ostream> きます。 using namespace std ー 商用クラスライプラリは機能の充実さを class CFi leCopy { typedef bO 引 ( *UserCancel Func ) ( const CFil ecopy& , void* 売り物にしようとするあまり , ついつい豪 typedef VOid ( *DirDownFunc ) ( const CFi ー ecopy& , const char* , void* typedef VOid ( *DirUpPunc) (const CFi leCopy&'const char*'void* 華絢爛な肥満体のクラスや深い継承レベル typedef VOid ( *Fi leCopyStartFunc ) ( const CFi ー ecopy& , const char* , void* typedef VOid ( *FileCopyEndFunc) (const CFileCopy&,const char*'V0id*); になりがちですが , PowerPIant は多重継承 public: CFiIeCopy( を取り入れることで痩せたクラス , 浅い継 virtual ¯CFiI ecopy ( 承レベルにするという , それまでの商用ク SetSourceDirName(const char*); / / コヒ。ー元のディレクトリ名をセットする VOid 〃コピー先のディレクトリ名をセットする SetDestDirName ( const char* VOid ラスライプラリとは違う考えで出てきたも SetCreateDir(booI); / / コピー先のディレクトリが存在しないときに作成する VOid 〃かの指定 のです。そのため学習しやすい体系になっ SetUserCancelFunc(UserCancelFunc); / / コピー中のユーザキャンセルの VOid / / 入力受け付けコールバック関数のセット ています ( それでも , 商用クラスライプラ SetUCFuncMisc(void*); / / ユーザキャンセルの入力受け付けコールバックの VOid / / 第 2 引き数のセット リ特有の機能充実主義のせいか , 今度は縦 SetDirDownFunc(DirDownFunc); / / ディレクトリ降下時に呼び出す VOid 〃コールバック関数のセット 軸ではなく , 横軸に広い体系になってしま SetDDFuncMisc(void*); / / ディレクトリ降下時のコールバックの第 3 引き数の VOid / / セット い , それが原因で学習しにくい点も否定で void SetDirUpFunc(DirUpFunc); / / ディレクトリ上昇時に呼び出す 〃コールバック関数のセット void SetDUFuncMisc(void*); / / ディレクトリ上昇時のコールバックの第 3 引き数の きません ) 。 / / セット void SetFiIeCopyStartFunc(FiIeCopyStartFunc); / / 1 つのファイルコピー開始時 / / に呼び出すコールバック関数のセット クラスのメンバ変数あるいは void SetFCStartFuncMisc(void*); / / 1 つのファイルコピー開始時のコールバック / / の第 3 引き数のセット メンバ関数が多い void SetFiIeCopyEndFunc(FiIecopyEndFunc); / / 1 つのファイルコピー終了時に呼 / / び出すコールバック関数のセット 深刻度☆☆☆ void SetFCEndFuncMisc(void*); / / 1 つのファイルコピー終了時のコールバックの / / 第 3 引き数のセット void ExecCopy(void); / / コピーする [ 症状 ] int GetErrorCode(void); / / 工ラーコードをえる 自分の使いたいメンバ関数を探すのに苦 労します。メンバ変数のからみが多いため 検証すべきメンバ関数が多くなり , プログ ラムがなかなか完成しにくくなります。ま たバグを発見しても直しにくくなります。 [ 原因 ] メンバが多いことによる弊害を認識して いません。複数のクラスに分割する方法を 知らないか , めんどう , メンバがたくさん あると豪華な仕様であり自分が賢いことを 証明できると勘違いしています。 [ 対策 / 予防 ] 継承を使って分割できないかを検討しま しよう。たとえばよく使う基本機能だけの クラスを先に作り , 派生クラスを作ってそ ちらに拡張機能を実装するのも手です。ま た , 複数のクラスに分離できるのなら , そ ちらを検討すべきでしよう。 1 static VOid TestMain(void) CFil eCopy theFC; theFC. SetSourceDirName( . SetDestDirName ( "YYY" theFC theFC. SetCreateDir ( true . SetUserCance ー Func ( MyUserCance ー Func theFC . SetUCFuncMisc ( NULL theFC . SetDirDownFunc ( MyDirDownFunc theFC . SetDDFuncMisc ( NULL theFC . SetDirUpFunc(MyDirUpFunc); theFC theFC. SetDUFuncMisc ( NULL . SetFi leCopyStartFunc(MyFi leCopyStartFunc); theFC . SetFCStartFuncMisc ( NULL theFC . SetFi ー eCopyEndFunc ( MyFi ー eCopyEndFunc theFC SetFCEndFuncMisc ( NULL theFC. if( 0 ! = theFC. GetErrorCode( ) ) { }else{ theFC. ExecCopy( if(0 = = theFC. GetErrorCode( ) ) { プログラミングの禁じ手 特集 1 15

2. 月刊 C MAGAZINE 2000年7月号

見られます ) 。しかしクラスを使わされる ほうはたまったものではありません。豪華 絢爛なメンバのうち , どれが自分の役に立 つものかを探すのがたいへんですし , さら にクラスが継承をしている場合 , 基底クラ スも探さないといけません。もちろん , の基底クラスも豪華絢爛であることはいう までもないでしよう : - ) 。 サイズの大きいメンバ変数を持つ クラスで自動変数を作る 深刻度☆☆ [ 症状 ] スタック領域に余裕のない組み込みやマ イコン , パソコンでは , プログラムのハン グアップや停止が起こります。 [ 原因 ] 余裕のない環境が存在することを知らな い , いわば無知によるものです。 [ 対策 / 予防 ] 走行させようとする環境の制約や限界を よく知ることです。 [ 例外 ] 仮想記憶で対応できたり , 余裕のある環 境ならよいでしよう。ただし移植性は低く なります。 [ 備考 ] 最近はマイコン , パソコンといえども走 行環境が充実してきましたが , だからとい って無限にメモリや記憶領域があるわけで はありません。これは C 言語編の「巨大な領 域を静的に確保する ( 2000 年 4 月号 45 ペー ジ ) 」のバリエーションでもあります 複数のメンバ関数をややこしく 利用する仕様にする 深刻度☆☆☆ [ 症状 ] プログラムが思ったように動作しなかっ たり , ノヾグがまぎれ込みやすくなります。 また , いったん完成したプログラムを修正 しようとしてバグが人ることがあります。 ややこしくすることが賢い証明だと勘違 16 C MAGAZINE 2000 7 [ 原因 ] いしていたり , ひとつの ( あるいは少ない ) メンバ関数でひとつの作業を完了させよう という意識がなかったり , 方法を知らない ことによります。 [ 対策 / 予防 ] ひとつの ( あるいは少ない ) メンノヾ関数で ひとつの作業を完了させるように設計しま [ 備考 ] なし。 [ 例外 ] しよう。 うになることも意味します。また複数のメ が多くなり , あとから検証するのがめんど はなく , メンバ関数の使い方の組み合わせ は量が多いから見通しが悪いというだけで メンノヾ関数が多いのは弊害ですが , それ 2 virtual -CaIIBack( CallBack( ) { mMisc = NULL; public: void* mMisc ー protected : class CaIIBack { class CFi leCopy { メンバ関数を減らす解答例 ンバ関数を操ってひとつの仕事をさせると いうのは一見「高度な」ことをやっていると いう錯覚にとらわれそうになりますが , た いていは設計不足や勘違いした自己満足 , あるいは仕様がよく固まっていないので自 己防衛的に複雑なメンバ関数仕様にしてい るなど , いろいろ考えられます。 何度もいうようですが , 複雑なものを作 ったり検証せざるをえなくすることは「賢 い」証明にはならず , 未熟である証明にし かなりません。 List 1 はひとつの作業 ( ここではファイル のコピー ) をするだけのために , わざとやや こしい仕様にしたものですが , こんなこと をするぐらいならひとつのメンバ関数で済 ませ , 実行する順番を間違えたり必要なメ virtua ー b00 ー UserCance Ⅱ void* ) = の / / コヒ。ー中のユーザキャンセルの 〃入力受け付け virtual void DirDown(const char*,void* ) = / / ディレクトリ降下 virtual void DirUp(const char*,void*) = / / ディレクトリ上昇 virtual ¯CFileCopy( CFiI ecopy ( public: ioMisc; } , void SetMisc(void* ioMisc) { mMisc virtual void FileCopyEnd(const char* ,void* ) virtual VOid FileCopyStart(const char*,void*) ・ / / Misc 引き数のセット / / コピー終了 = の / / 1 つのファイル / / コピー開始 ー / / 1 つのファイル MyFiI eCopyCa Ⅱ Back theca Ⅱ Back; CFi IeCopy theFC; static VOid TestMain(void) virtual VOid FileCopyEnd(const char* ,void* virtual VOid FileCopyStart(const char* ,void* virtual void DirUp(const char* ,void* virtual void DirDown(const char* ,void* virtual bo 引 UserCance Ⅱ void* public: class MyFiIeCopyCaIIBack : public CFileCopy: :CallBack { int ExecCopy(void); / / コピーする 土 n し setparam ( const char* , const char* , b001, Ca Ⅱ Back* ) , ・ / / パラメータをセットする = theFC. ExecCopy ( ) ) { if ( 0 )else{ if( 0 ! = theFC. SetParam( nxxx", ” YYY" ,true,&theCallBack) ) {

3. 月刊 C MAGAZINE 2000年7月号

十五ロ の 0 ところで構造化というのは簡単にいい切 まで「手続き」レベルになるのが特徴であり , オプジェクト指向をさほど理解していな ってしまえば「プログラムコードの構造化 , それが同時に別の問題を生むことになるの いが構造化プログラムは得意な設計者の場 体系化」を意味します。複雑な手続きや関 ですが。 合 , 作ったクラスを見ると , これまた「ク 数を物理的 , 意味的にふたつ以上のモジュ 一方 , オプジェクト指向でいうクラス化 ラスを構造体と勘違いしている」の別バー ールに分割していく「分割統治」のひとつの は , 必要となるオプジェクトの抽出作業を ジョン , すなわち「クラスをモジュールと勘 例です。この場合 , 分割する基準は , あく したあとで , そのオプジェクトに必要な属 違いしている」パターンに陥っているケー 性やメソッドなどを付加していく作業です。 スがあります。たとえば List40 は極端な例 C + + で組み直した例 「何をオプジェクトとするか ? 」というのは ですが , これを見て違和感や「気色悪さ」を #include <stdio. h> オプジェクト指向プログラムでの中心的な 感じないとすれば , その人はクラスに対す c lass Anima に lass ( テーマですが , それだけに的確にオプジェ る感覚が発達していないのでしよう。ここ public: void Naku(void); クトを見い出せるようになれば , ほば , そ で使われている TextProcClass はどうやら文 void Aruku ( void の人はオプジェクト指向を理解していると 字列の処理をするつもりなのですが , よく 考えて差し支えないでしよう ( しかし , 見ると , 単なる文字列処理サプルーチン集 の「オプジェクトの見い出し」が難題なんで に成り下がっています。これだったらクラ すけどね : - ) ) 。 スにする必然性など何もなく , 各メンバ関 クラスをモジュールと勘違いしている例 class TextProcCIass 0 public: void Toupper(char* 土。で ext ) に〃文字列の大文字化 / / 冖を ( 中略 ) . 。を void Tolower(char* 土。で e ) ( / / 文字列の小文字化 〃冖中略 ) .. 土北 Length(const char* inText) { / / 文字列の長さをえる / た . コ中略 ) .. return ::strlen(inText); void Left(const char* inText,char* OutRes,int 土 n い ( //BASIC の LEFT$ と同じ void Copy(const char* 加 S てら cha て * ou に Des ら土土員いい″文字列の部分コビー Ⅳ 0 土 d AppendText(char* ioText,const char* inAdd) ( / / 文字列を追加する static void TestMain ( void ) TextProcCl ass theTextProc; char theProc[PROC—LENGTHl; char theWork[WORK—LENGTH]; 〃ワークから左 10 バイトだけを取り出し処理文字列にうっす / / 10 バイトない場合は足りない部分を空白で埋める 土北 theLen = theTextProc. Length(theWork); if(theLen > 10 ) { theTextProc. Copy(the?roc,theWork,10) ・ )else{ theTextProc. Copy(theProc,theWork,theLen); for(theLen 哥 10 ー theLen; theLen > の theLen-—) ( theTextProc. AppendText(theproc," 第 〃さらに処理文字列を大文字化する theTextProc. Toupper(theProc); List 39 class DogCIass : public AnimalCIass { public: void Naku(void); class catclass : public AnimaIClass { public: void Naku(void); ånt main ( void) / * 変数定義 * / DogC lass 20 CatCl ass *aCat; / * 手続き来 / aDog 哥 new DogCIass aCat = new CatClass; p て i f ( ”にゞ ) Naku! つ printf(hdog = aDog->Naku( printf ( "cat ー acat->Naku( p て土北 f ( ”に二 ) Aruku! の printf ("dog = aDog->Aruku( 嵭 printf (ficat = aCat->Aruku(); del ete aDog; delete aCat; て e し u てれ 0 ー List 40 void AnimaICIass: :Naku(void) pr 土 n ( ” ( て 0 て ) ” void AnimalClass: :Aruku(void) printf(nteku,teku,teku,teku. void DogCIass: :Naku(void) printf("wan,wan,wan... n ” void CatC lass : : Naku ( void ) printf( "Nyan,Nyan,Nyan ・ プログラミングの禁じ手 特集 1 35

4. 月刊 C MAGAZINE 2000年7月号

List 関数ポインタが使えない typedef void CallBackFunc(void); void WeUseCal lBack(CaIIBackFunc inF) inF()'i . い中略 ) 、、 cl ass TestC lass { . コ中略 ) 冖 pub : void MyCaIIBack(void) ( gout くく -"MyCaIIBack*nn; void TestCaI IBack(vOid) { : :weUseCal IBack(MyCaIIBack) 引 ~ / / ←工ラーになる List メンバ関数を間接的に使う例 class TestClass { static TestC lass* slnstance; public: void MyCaIlBack(void) { cout くく "MyCaIIBack*nn; void TestCaIlBack(void) ( ー slnstance = this; ;:WeUseCal IBack(Static—MyCaIIBack); static void Static—MyCa Ⅱ Back ( void) { sInstance->MyCal lBack( リ 数をひとつずつ独立した普通の関数にして も差し支えありません。 通常 , クラスにするというのは , そこか らインスタンスを発生させ , そのインスタ ンスに対してメンバ関数を通じてアクセス するという感覚になるはずです。単なるサ プルーチン集の代わりになっているという のは , そこらへんの感触が , まだ発達して いないわけです。 そういえば , インスタンスの感覚欠落症 の具体例として , たまにパソコン通信やイ ンターネットなどで見かける質問ですが , 関数へのポインタを要求された場合 , メン バ関数を指定するとエラーになるのはなぜ だというのがあります (List 41 ) 。しかし , これはエラーにせざるをえないのです。な ぜならメンバ関数というのはインスタンス の持ち物であるため , 工ラーにしなかった 場合「どのインスタンスのメンバ関数とし て実行するのだ ? 」という問題にぶつかる からです。また , キャストで逃げても本質 的な解決にはなりませんし , まともに動く 保証などありません。たまたま動いたとし ても , それは運がよかっただけの話です。 この例題の状況では , 特定のインスタンス C + + と C 言語で NULL の定義を変える例 List 36 C MAGAZINE 2000 7 43 *endif #define NULL ( (void * ) 0 ) # 引 se #define NULL 0 #ifdef —-cp lusp lus に所属しない sta ⅱ c メンバ関数を指定する か , 普通の関数を指定し , その関数内でイ ンスタンスをはっきりさせてからメンバ関 数を使うか , です。たとえば , List 42 のよ うな感じでしようか。 C + + は C 言語の完全な 上位互換性があると思い込む 何をいうんだ , このおっさんは ? と怒 られそうですが , 細かい部分で完全な上位 互換性が損なわれているのも事実だからで す。だからといって , よく取りあげられる 「 sizeof('X') が C 言語では sizeof(int) と等し いが , C + + では sizeof(char) と等しい」とい う重籀のスミつつきレベルだけでなく , 根 本的に何か違うという部分もあります。 ひとつの例をあげれば「 NULL の扱いが違 う」というのがあります。禁じ手の C 言語編 で「 NULL とゼロを間違える ( 2000 年 4 月号 24 NULL にキャストが必要な例 void Hoge ( 加土れ I ) 00u しくく fiHoge(int ”くく土 n 工くくつ第 void 日 oge ( CO れ s し char* 土れ T ) if(NULL = 土心 ) { cout "Hoge(NULL)%nn )else{ List 44 ページ ) 」を紹介しましたが , C + + ではより 間違いやすい状況になっています。という のも C 言語の場合 , コンパイラの提供者が NULL の定義を , #define NULL( (void * ) 0 ) というように , ポインタであることを明確 にしているケースもあったのですが , C + + の場合 , この方策では問題が出てきます。 というのも , 型チェックが厳しくなったた この定義による NULL をポインタ変 めに 数に代入しようとすると「 void ポインタを , この型のポインタ変数に代入できない」と いう意味のエラーを出し , 使い物にならな いからです。かといって C + + の事情に合わ せて , 型ごとに無効ポインタの定数を用意 するというのもめんどうな話です。それで 苦肉の策として , 0 を「ポインタがオプジェ クトを参照していないことを示すポインタ リテラル」にする ( 『プログラミング言語 C + + 』 CO リセくく "Hoge(const 0 れ 4 て * ”くく土 n < くつ基 n ”・ statie VOid TestMain ( VOid ) Hoge(NULL); / / ←呼び出すのは Hoge ( i ) なのか Hoge ( eons セ char*) なのか

5. 月刊 C MAGAZINE 2000年7月号

十五ロ の 0 の 5.1.1 「ゼロ」を参照のこと ) という規格に ものが多数出回っています。したがって規 例で説明しましよう。 CountPeople クラス しました。もちろん , これでは , ますます 格上は , こうだから , こうなるという決め は性別のカウントしかできないのが不便で , 数値のゼロと無効なポインタの違いがわか つけもしにくいものがあります ( たとえば広 同時に成人と未成年のカウントもしたいと りにくくなり , ある意味 , 退化したとも思 く使われている某 C + + では , new が失敗し 誰かが要求したとします。そこで継承によ た場合 , bad ー a11 。 c を投げずに , 独自の例外 って機能拡張をした ExCountPeople クラス えます。 そういうわけで , C + + の場合 , コンパイ オプジェクトを投げています。よそで開発 を作ってみましよう。たとえば , List 45 の ラの提供者は NU 乢の定義を , した bad ー a Ⅱ oc を投げる前提のライプラリを ようにすれば簡単に作れますが , ところが 使う場合は要注意です ) 。 これにはとんでもない欠点があります。と #define NULL 0 としているわけです ( なかには C + + と C 言語 いうのも , たしかに成人と未成年のカウン また C + + の規格を参照するための標準書 トはできるものの , 成人の女性だけとか未 で定義を切り替える工夫をしている提供者 と思われている『プログラミング言語 C + + 』 もいます。たとえば , List 43 のように ) 。 も 1 版から 3 版までありますが , それぞれ規 成年の男性だけの数などが調べられません。 格が食い違っている部分があります ( 一時 成人と未成年のカウントもしたいと要求し ところが , こんなことをすると問題にな るのは「 NULL にキャストをしないといけな は , 通称「 ARM 」と呼ばれていた『羽 no た誰かは , きっとあきれてしまうことでし よう。継承によるアプローチの場合 , 機能 い」という , ちょっと間抜けなパターンも tated C + + Reference ManuaI 』が標準書のよ 出てくるのです。たとえば , 関数のオーバ の加算 , 減算はできるものの , 根本的に機 うに扱われた時期もあったのですが , 現時 点でも , 神通力があるかどうかは微妙なと 能が異なるケースでは期待ハズレに終わる ロードがからんでくると , List44 の TestMai ころです ) 。したがって , 巷に出回ってい 場合があります。 n 内で記述している Hoge は「 void Hoge (int) 」 よくいわれるのが「 is - a 」関係であるなら継 なのか「Ⅷ id Hoge(const char* ) 」なのかと る C + + の参考書自体 , どれを信じていいの いう問題が出てきます。 NULL を 0 で定義し か , という問題もあります ( それをいい出 承は使えるが , そうでないなら使えない。 た場合は無効ポインタではなく , 単なる整 すと , この記事自体・・・・・・ ) 。 もし「 has - a 」関係なら所有のアプローチをす 数 0 と解釈され , 「 void Hoge (int) 」が呼ばれ ただ筆者の個人的意見では , とりあえず べきということです。「 is - a 」関係というのは 文字どおり「 A は B である」がいえる関係のこ るわけです ( ちなみに筆者が gcc で試したと は『プログラミング言語 C + + 第 3 版』を信じ , とをいいます。哺乳類クラスを継承して人 ころでは「 void Hoge (const char * ) 」が呼ば 実際に使うコンパイラの挙動が , どうも規 間クラスを作る場合 , 「「人間』は『哺乳類』 格と違うと判断した場合は , それを考慮に れました。 Code Warrior は「 void Hoge (int) 」 入れ , 落とし穴にハマらないように防衛的 である」ということがいえます。 が呼ばれました ) 。したがって無理にでも 「 void Hoge (const char * ) 」を呼びたいなら , な運営をしよう , としかアドバイスできな 継承で ExCountPeop クラスを作る いのです。 Hoge (static cast<const char * > class ExCountPeopIe : public CountPeople { (NULL)); 土 n し d 田い / / 成人の数 土 mChild; / / 未成年の数 とでもすればいい話なのですが , それ以前 継承と所有を混同する void AddFemale(int); / / ( わざと未定義に ) に , このような関数のオーバロード自体を void AddMale(int); / / ( わざと未定義に ) public; 継承というのは , すでに存在するクラス やめたほうがいいという意見もあります 取 Co 聞し peop 地 ( ) { Reset( ) void Reset( ) { を元に , 新しいクラスを「差分」で作ってい (fEffective C + + 』の 25 項「ポインタと数値型 mAdult = mChild = の countpeople: :Reset( にオーバロードするのは避けよう」を参照 く方法ですが , これも注意しないとかえっ 土北 GetAduIt(void) const { return d 朝 に GetChiId(void) const { return mChild; て手間を食う場合があります。たとえば自 のこと。また「プログラミング言語 C + + 第 3 void AddAduItFemale(int inAdd){ CountPeople: :AddFemaIe(inAdd)i 版』の 7.4 「関数名の多重定義」も参照のこ 動車クラスを実装する場合 , タイヤクラス mAdult 十 inAdd; と ) 。 やエンジンクラスという , できあいのクラ void AddAduItMaIe(int inAdd) ( CountPeople: :AddMaIe(inAdd); スがあったとしても , これを継承して作る mAdu に十 = inAdd; 利用する C + + コンバイラが 手法は取らないはずです。なぜなら , タイ void AddChiIdFemaIe(int inAdd) ( CountpeopIe: :AddFemaIe(inAdd); 『プログラミング言語 C + + 』の ヤもエンジンも自動車の部品であり , これ mChild 十 = inAdd; 第何版に準拠するか知らない らは自動車クラスに「所有」するという形で void AddChildMale(int inAdd) { CountPeopIe: :AddMale(inAdd); mChild 十 = inAdd; 実装するはずですし , そうしないと変なこ ようやく標準化が終わったとはいえ , C + + コンパイラは , 現実にはコンパイラを作成 とになるからです。 した時点での規格 , あるいは fEmbedded すでに説明した CountPeople クラス (List C + + 』のようなサプセット規格 , 独自規格の 4 ) を元にし , そこから新しいクラスを作る List 45 プログラミングの禁じ手 37 特集 1

6. 月刊 C MAGAZINE 2000年7月号

先ほどの自動車クラスの例でいえば , タ イヤクラスを継承して自動車クラスを作ろ うとするのは「『自動車』は『タイヤ』である」 ことにしようとする明らかに変な行為です。 「『自動車』は「タイヤ』を所有する」というの なら正しいのです。つまりタイヤクラスと 自動車クラスは「 has - a 」関係です。 では , ExCountPeople クラスと CountPeo ple クラスの関係をもう一度考え直してみる と , 「 ExCountPe 叩 le クラスは , 成人をカウ ントする専門の CountPe 叩 le オプジェクト と , 未成年をカウントする専門の C 。 untPeo ple オプジェクトを「所有』する」という「 has - a 」関係だと考えればいいわけです。つまり , List 46 のようにしたほうが望みどおりのク ラスになるわけです。 オプジェクト指向の導入メリットである ほ 差分プログラムを採用する姿勢は褒められ るべきですが , その適用方法は何も継承だ 今回の記事を書くにあたって , 最初に項 目出しをしたのですが , あまりにも数が多 くて , 半分ぐらい削りました。削ったおも な理由は「上級者でないとハマらないし , ハ マっても上級者だから逃げられる」とか「精 神論になってしまうので説得力がない」な どです。まだまだ書き足りませんが , あま りいっぺんにあれこれ書いても , その量の 多さにうんざりされるのがオチでしよう。 そこで , この記事だけでは物足りないと いう読者のために , 以下の文献も紹介して おきましよう。とくに fEffective C + + 』シリ ーズは C + + で仕事をするプロは必読である という意見もありますが , まだまだ知らな い人がいるようで残念です。『プログラミ ング言語 C + + 』は , ちょうど C 言語プログラ マにとっての『プログラミング言語 c 』 ( 通称 K&R) と同様の位置付けの本で , c + + プログ ラマにとって必読といわれているものです。 分量が多いのが難点ですが ・「プログラミング言語 C + + 第 3 版』 , アス キー , Bjarne Strous れ p 著 / 長尾高弘訳 , 旧 BN4-7561-1895- X C + + の変遷は目ざましいので , 常に最新 38 C MAGAZINE 2000 7 LiSt 46 所有で ExCountPeop クラスを作る cl ass ExCountPeop 厄 { CountPeople d ⅵ〃成人のカウントオブジェクト countpeople mChild; 〃未成年のカウントオブジェクト public: ExCountPeople( ) { void Reset( ) { mAdult.Reset( mChild. Reset( int GetAdult(void) const { return み du 比 . GetTo int GetChil d ( void ) const { return mChil d. GetTota Ⅱ ) void AddAdultFemale(int inAdd) { mAdult. AddFemale(inAdd); void AddAdultMale(int inAdd) { mAdult. AddMale(inAdd); } を void AddChiIdFemale(int inAdd) { mChild. AddFemaIe(inAdd); void AddChiIdMaIe(int inAdd) { mChiId. AddMale(inAdd); int GetTotal(void) const { return mAdult. GetTotal ( ) 十 mChild. GetTotal ( ) 土 n と GetFemale(void) const { て eturn mAdult. GetFemale( ) 十 mChild. GetFemale( 土 n に GetMale(void) const { return mAdult. GetMale( ) 十 mChild,GetMale( ) int GetAduItFemaIe(void) const { return mAdult. GetFemale( ) int GetAdultMale(void) const { て eturn mAdult. GetMale( ) int GetChiIdFemaIe(void) const { return mChild. GetFemale( ) int GetChiIdMale(void) const { return mChild. GetMale( ) ・ けにかぎらず , 「所有」やテンプレートなど のほかのアプローチでも可能なことを知っ ておきましよう ( 『 Effective C + + 』の 35 項「 pub ⅱ c に継承するときは " その一種である " 関係 参考文献 の版で規格を確認する必要があります。第 3 版になって標準ライプラリの仕様の説明 が詳しくなったのをはじめ , いままで曖味 だった表現が改善され進歩の跡が見られま すが , 同時に仕様拡大ゆえ , 分厚いページ 数になってしまったのが痛いところです。 そのため最後のページを読み終える前に読 者が挫折してしまい , 後半の重要な話題を 読む前に投げ出してしまうのではないかと 危惧します。たとえば第 4 部「 C + + を使った 設計」は C + + にかぎらないプログラム設計に 関する重要な話題が書かれているので , こだけは個人的に , ぜひとも読んでもらい たい箇所です。 ・『 Effective C + + 改訂 2 版』 , アスキー , Sc ott Meyers 著 / 吉川邦夫訳 , lSBN4-7561- 1808-9 ・『 MO 「 e Effective C + + 』 , アスキー , scott Meyers 著 / 安村通晃 , 伊賀聡一郎 , 飯田 朱美訳 , 旧 BN4-7561-1853-4 C + + プログラムをする際の落とし穴やノ ウハウについて詳しく書かれた名著です。 この本の著者が書いているとおり , C + + の 機能を完全に理解し十分に使いこなすのは のモデルかどうか確認しよう」と 40 項「 " そ れを持っている " 関係や " それを実装手段と する " 関係は層を重ねる形でモデル化しよ う」を参照のこと ) 。 難しいことです。単に C + + の文法面や重籀 のスミつつきパターンは多いのですが れは「軍事オタクが書いた戦争論」のような ものです。それに対し『 Effective C + + 』シリ ーズは「本物の軍人が書いた戦争論」の違い があります。 ・『 C + + オブジェクトモデル』 , トッパン , Stanley B. Lippman 著 / 三橋ニ彩子 , 佐治 信之 , 原田曄訳コ SBN4-8101-8101-4 本記事で「理念と実装の混同」を指摘しま したが , 困ったことに , じゃあどれが「理 念」で , どれが「実装」なのか判断がつかな い人も少なくはないでしよう。この本は普 通の C + + 本とはかなり毛色が違い , プラッ クポックスのなかに閉じ込められている C + + の「実装」について詳しく書いたものです。 内幕を暴露するぞ的なところが , とてもお もしろいのですが , ある意味 , 取り扱いを 間違えると危険きわまりない面があります。 危険というのは , この本の内容のことでは なく , 読者が「誤読」する危険のことをいっ ています。つまり , よけいに「理念と実装 の混同」をこじらせる可能性があることで す。

7. 月刊 C MAGAZINE 2000年7月号

List 違うクラスのメンバをキャストでむりやり使う例 if( く = *thep & & *thep く = ' 2 ' ) { for(thep = mText; *thep; thep 十十 ) { char* theP; if(mText) { void Ex2TestClass: :Toupper(void) void Toupper ( void 曲は c : clasg Bx2TestCIass : public TestCIass ( *theP = *theP - gtatic void TestMain(void) ExTestCIass theT(ntest"); ( (Ex2TestClass* )&theT)->Toupper( ) , cout くく theT. Get( ) くく endl; 違いしています。 [ 対策 / 予防 ] ・ / / ←無理やりキャストで使う List リファレンス変数の変な使い方 void Ex2TestCIass: :Toupper(void) if(mText){ c て & thep; / / ←初期化がないので、コンパイル時にエラーになる for(theP mText[01; thep; thep 十十 ) { 土 f ( ' 記← thep & & thep ← 'z'){ thep ま thep 十 'A' List 29 無効リファレンスにしたつもり 〃土 n 報 theIR = UL は見かけ上い↓のように解釈される int& theIR = thel; int thel = リファレンスの望ましい使い方を知るこ 28 C MAGAZINE 2000 7 必要」という意味のエラーが出ます。つま ります。「リファレンス変数には初期化が 結論からいうとコンパイル時にエラーにな まともにコンパイルできるでしようか ? ところで List 28 のようなプログラムは , 「 & 」に変えただけだよ , みたいな。 アクセスできるポインタ , あるいは「 * 」を とし穴にハマります。単に「 * 」が取れても タ」という妙な認識や説明をしていると落 スで通します ) というのは「 C + + 版のポイン リしなくなる危険があるので , リファレン のか , C + + の reference の訳語なのかハッキ かを参照するという本来の日本語の意味な 訳されています。また「参照」だけでは , 何 語 C + + 第 3 版』日本語版ではリファレンスと れることもありますが『プログラミング言 リファレンス (reference は「参照」と訳さ [ 備考 ] なし。 [ 例外 ] 強するべきです。 ンスとポインタの厳密な違いというのを勉 というとらえ方をしているなら , リファレ とです。リファレンスはポインタの C + + 版 りリファレンス変数を使う場合 , 必ずどの オプジェクトを参照しているかをハッキリ させないとダメです。だからといって , 「 ch ar& theP = mText[0]; 」としても今度は「 the P + + 」と記述した箇所が問題になります。な ぜなら参照しているアドレスを増やすので はなく , 参照しているオプジェクト ( この 場合 , mText [ 0 ] ) の値をひとつ増やすだけ だからです。 そのものを加減する ではなく , 参照しているオブジェクト 算をした場合 , アドレスを加減するの ③リファレンスはポインタと違い , 加減 値を取ることもない ②リファレンスはポインタと違い , 無効 ならない 初期化が必要である。そのため不定に ①リファレンスはポインタと違い , 必ず いままで述べた点をまとめると , List 29 のようになったかのようです。 事のあとのほうで説明します ) , 見かけ上 , くなっています。これに関しては , この記 違いと混乱の元ですが , さらに状況がひど と解釈され ( C + + の場合も NULL の解釈は勘 ムダです。この場合 , NULL が数値のゼロ 感覚で「ⅲ t & theIR = NULL; 」などとしても またポインタ変数を無効にするのと同じ List 30 無効ポインタを避けるテクニック void f(ehar* 土 ) List リファレンスの代入で問題の起きる例 土 n に thel; / / 不定値になづているい といったあたりでしよう。ですから , ポイ ンタが無効でないのを確認するコード , た とえば List 30 のようなもののリファレンス 版というのは「普通は」考えられません。リ ファレンスが引数の関数の場合 , そのリフ ァレンスが無効であるかというチェックや 配慮をせずに安心して中身をプログラムで きるはずです。 ところで「普通は」と書いたのは , 普通じ ゃない場合もあるからです。たとえば , ど こかのバカが List31 などのようにむりやり 土れ t & theIR = *thel; int* = ー〃無効値になっている ! int& theJR = *theJ;

8. 月刊 C MAGAZINE 2000年7月号

J a フ恤クラミングリファレンス 詳説 JDK 解体新 呼び出し対象オプジェクトが特定されて パイルせずに古いバイナリを使うと実行時 static メソッドはコンバイル時に解決されている いれば , それにロックをかける。さもなけ にエラー (NoSuchMethodError) になる。 れば (static メソッドの場合 ) , ロック対象は Fig. 9 の ( 1 ) からもわかるように , 呼び出 P 「 ivate メソッドは仮想起動しない し対象メソッドが s ねⅱ c の場合 , それがどの 該当のクラスオプジェクトである。また該 F1g. 9 の ( 3 ) では , 起動モード "nonvirtual 当メソッドの実行が正常終了であれ中断終 クラスに属しているメソッドであるかはコ 了であれ , 完了したあとにはロックは自動 ンパイル時に決定されている。より正確に の場合の説明をしている。その影響を示す いえば , static メソッドの呼び出しコードが 的に解除される。 のが List 4 である。クラス F00 のなかで , メ 含まれているコンパイル単位をコンパイル ソッド b と zot を定義している。 baz は prote 注 . すべき動 した時点で , どのクラスに従属するメソッ cted であり , zot は private である。またこれ ドを呼び出すかが決定され , クラスファイ らを間接的に呼び出すメソッド callBaz と 以上の仕様からの帰結として , 呼び出す caIIZot を定義している。それから , F00 を ルのなかに指定される。したがって , 呼び 出される側のコンパイル単位で s ねⅱ c メソッ べきメソッドの解決については注意を要す extend したクラス Bar においても , 同様に ドを定義する位置をクラス階層の上に移 protected baz と private bar を定義している。 る動作が生じる。それらのいくつかについ て最後に簡単に触れておこう。 すような変更を行い , 呼び出し側を再コン そして , メソッド main のなかでは , シグネチャはコンバイル時に定まっている s 起動モード non ⅵ ua に起動モードⅵ「 tu 引 / * List5. javä * / * シグネチャはコンパイル時に定まっている class F00 ( public 0 土 d 20 日 in セ n, double x) { SY em. ou し prin れ ( ” F00. zo こ ( (int)* public void zot(byte b, do 曲地 x) ( system. out. printIn("F00.20日 (byte)" 十 b 十な public void qux(int れ , double x) { / * st4. java * / * 起動モードれ onv 辷 tu 引と起動モード v 近 t ー class F00 { protected void ) ( system. out.println( ” F00. baz っ private void 名 0t ( ) ( system. ou し . p て土 n 目 n ( ″ F00.20 セつ public void caIIBaz( ) { / / v 土て tu モード baz(); public void ca 日 20 セけ ( 〃れ 0 れ辻 u 引モード 20t ( リ 十れ十 % (double)" 十 x 十つ” ) ・ ( do 曲厄ド + x + 物尸わ ( do 面厄尸十十つつ・ system. 0uしPてin凵n(”F00. qux((int)" 十 n 十” class Bar extends を 00 { public void zot(byte b, int m) ( system ・ out.println("Bar. 20t( (byte)" 十 b 十 public void x ( 土北 n, dO x) { system. out. printIn("Bar. qux( (int)" 十 n 十 public void qux(byte b, do 面 le 幻 ( class Bar extends を 00 { protected void baz( ) ( system. out. println("Bar. baz 0 て土 va セ e void 20t ( ) ( system ・ 0uしPて土n日nに Bar. 20 し (int)" 十 m 十つ″ ( do 面厄尸十 x 十”ド system. out.println("Bar. qux( (byte)" 十 b 十第 , (double)t 十 x 十物尸リ 0 lass List4 extends Bar { public static void main(String[J args) F00 myFOO = new F00 ( ) ー て myBar new Bar( system. out. println( ” F00 つ myF00. caIIBaz( リ myFoo. callZot( system. out. println(" system. out. printIn("Bar"); myBa て . caIIBaz( myBar ・ callZ0t( system. out. println(* system. out. Pて土n凵nにF00 but holds a Bar" リ myFoo ま myBar; myF00. caIIBaz( myFOO. ca Ⅱ 20 日 c lass も土虻 5 { public static void main(string[ ] args) F00 myFOO ま new F00 ( ) ・ Bar myBar = new Bar( byte b ま 1 の system. out. Pて土n凵nにF00”リ myF00. zo 日 10 , 20 myF00. zot(), 20 リ myFOO. qux(10, 20 myF00. qux(), 20 リ System ・ 0uしPてin日れに system. out.println("Bar") 引 myBar. Z0t(10, 20 ) : myBar. zot(), 20 myBar.qux(10, 20 myBar. qux(), 20 潺 system. ou し printl n に system. out. println( ” F00 but holds a Bar"); myFoo = myBar; myFOO. zot ( 10 , 20 myF00. zot(), 20 myFOO. qux(10, 20 リ 叫 F00. q 皿 ( b , 20 ) を 1 18 C MAGAZINE 2 0 7

9. 月刊 C MAGAZINE 2000年7月号

考慮すると C + + しか選べないというのが実 情です。 筆者自身はいままで複数のプラットホー ム , 複数のプログラム言語を経験していま すが , C + + は最良とはいわないものの , ほ ぽよく , それと同時に最悪な ( 笑 ) プログラ ム言語だと思っています。最悪というのは , 使いこなしが難しく , どういうスキルの人 がくるか予想のつかないチーム開発では , あまり使いたくないというのがあるからで す。事実 , 筆者は過去にⅥ sual C + + のプロ グラムで目も当てられないような悲惨なコ ードを見たことがあり , 同時にこれが珍し い例外などでは決してなく , 日本の ( おそ らくは日本以外でも ) 現場では , わりあい にありふれた例であることを知ったからで す。だからといって C 言語のほうがマシと いいたいわけではありません。冒頭で述べ たように C 言語が自動車なら , C + + は飛行 機なのです。飛行機には飛行機の運用方法 があり , 勘違いして自動車の運用方法を適 用すれば墜落するのは当たり前なのです。 ということは , まったく新しいものに出 会ったのだと解釈して , 新しい対応と運用 を考えないといけないのです。しかし , うはいっても , どうすればいいかよくわか Objective-C の実例 #include く stdio. h> *include <0bjc/0bject.h> €interface AnimalClass : Object List 38 / * 手続き * / id acat; id aDog; / * 変数定義 * / 土 n し main ( void ) gend (void)Naku; @interface CatC lass : Animal C lass gend (void)Naku; @interface DogC lass @end (void)Aruku; ( void )Naku; : AnimalClass 34 C MAGAZINE 2000 7 [ aCat Naku 島 printf( "cat = [aDog Nakul; p て intf( "dog ー printf( ″につ Naku! \ n ” aCat = [CatClass new]; aDog = [DogClass new]; らない , という人たちが多いのも事実なの ですが。 このパターンは思想的な面が強く , 症 状や深刻度などは見積もりにくいので , ほ かの禁じ手のような症状 , 深刻度 , 原因な どは記述せず , 名前と , その解説で済ませ たいと思います。 クラスを構造体と勘違いする これはイヤになるぐらい多い事例です。 なぜ , これが問題なのかというと , まさに 「理念と実装の混同」の典型例であり , クラ スが構造体という思い込みゆえ , できあが るプログラムが「構造体に毛のはえた使い 方」という , 飛行機を自動車のように地上 で走らせているだけの使い方におとしめて いるからです。 そもそもクラスというのはインスタンス を発生させるための土台であり , これを構 造体で実装するか , あるいは違う方法で実 装するかには関係のない「理念」の話です。 たとえば , Objective-C という , C + + 同様に オプジェクト指向を目指したハイプリッド 言語がありますが , この言語ではクラスの 表現は構造体の拡張ではなく , 独自文法で 行っています。たとえば List 38 のように クラスの宣言が「@interface—@end 」で囲ま れた部分で行われ , C + + のメンバ関数に相 当するものの実装を「@implementation—@ end 」で囲まれた部分で行っています。これ を見るかぎりでは「クラスとは構造体のこ となんだ」という勘違いは起こりそうにあ この Objective-C のプ りません。ちなみに ログラムとほば同じものを C + + で組むと , Li st 39 のようになるでしようか。 構造化とクラス化を勘違いする 大規模プログラムの対策として , 過去考 えられてきた方法として「構造化プログラ ム」があげられます。これは過去の実績だ から , もはや時代遅れと決めつけるのは完 全に間違っています。いまでも有効な考え ですし , 実をいうと筆者は , オプジェクト 指向どうのこうの以前に , 先に「構造化プ ログラム」の理念を理解すべきじゃないか といいたくなるのが , 日本のプログラム開 発現場の大半の実情ではないかとすら思っ ています。「体育会系のノリで頭を使わず体 カ勝負でむりやりプログラムをするのが諸 悪の根源だろうが」といいたくなるような。 printf(" にゞ ) Aruku! つ printf( "dog ま” [ 20g Aruku]; printf( ncat = [aCat Aruku]; [ aDog free 嵭 taCat freel; return @implementation AnimalClass (void)Naku { printf( ” ( e てて 0 て n つ (void)Aruku { printf("teku,teku,teku,teku. gend @implementation DogClass ー (void)Naku { printf( "wan,wan,wan... \ n ” ) @end @imp lementation CatC lass (void)Naku { printf("Nyan,Nyan,Nyan... ” ) @end

10. 月刊 C MAGAZINE 2000年7月号

ている挙動 ( メソッド ) とは分けて考えてい して , 通常は「メッセージ = メソッド」とし ます。すなわち , メッセージとメソッドは てしまい , 効率の悪い「メッセージ受信→ 別物という考えです。たとえば「キスをす 解釈→メソッド起動」をやりたい場合は「仮 る」というメッセージがあった場合 , これ 想関数」を使いなさい , ということにして を受け取る異性オプジェクトのメソッドは います。つまり C + + では , デフォルトは静 必ずしも固定していません。自分の奥さん 的 ( 固定 ) で , 動的なことをしたい場合は仮 メモリリークなどの予想しない資源の解 オプジェクトが「キス」メッセージを受けた 放し忘れ現象や , その結果で起きるバグに 想関数です。 悩まされます。 場合は問題ないのですが , 見知らぬ女性オ ところがこの方策は , いちいち動的なも [ 原因 ] プジェクトだと「殴る」メソッドを起動する のをするなら仮想関数と明示しないといけ かもしれません ( 「キス」メッセージを送る デストラクタを virtual にしないクラスを ないのがめんどうです。うつかり忘れて , 継承し , 仮想関数を利用すると弊害が起き オプジェクトがキムタクの場合は , すべて 意図しないメンバ関数を呼び出したり , メ る場合があることを知らないせいです。 の女性オプジェクトが「喜ぶ」メソッドを固 モリリークや資源の解放忘れを引き起こす [ 対策 / 予防 ] 定的に起動するのかもしれませんが : - ) ) 。 のが問題です。たとえば , List19 のような 勉強しなさい : - ) 。 このように受信オプジェクトによっては , ケースでは , 基底クラスである limal クラ [ 例外 ] ひとつのメッセージによって起動するメソ スのデストラクタがⅵれ ual でないため , the 継承させたりしないと決めているか , 継 ッドはひとっとはかぎらず複数である可能 limal でポイントしているオプジェクトが 承はさせるものの仮想関数をいっさい使わ 性があります。ところがこういう仕組みを Dog オプジェクトなのに , きちんと Dog ク ないと決めている場合は , かまわないかも 実現しようとすると , メッセージの受信と ラスのデストラクタが呼ばれません。画面 しれません。しかし , あとで状況が変わっ メソッドの起動をテープル化し , 動的に反 に出てくるメッセージは , て「しまった ! 」とならないように 応する仕組みを作らないといけませんし , * test start * [ 備考 ] メッセージ受信→解釈→メソッド起動によ Animal Start たいていのオプジェクト指向言語では , って , プログラムの実行速度を落とすハメ Dog_Start オプジェクトに対してある命令を送ること になります。 C + + は C 言語の「効率至上主義」 BOW! WOW! ( メッセージを送ること ) と , オプジェクト ( 効率のためなら自分の親兄弟を殺しても Animal End 知らないよ主義 ) を守るための苦肉の策と が外部の働きかけに反応するために用意し * test end * テストラクタがⅵ「 tu 引でない new ではなく ma Ⅱ oc を使った変な例 class Äれ土 m ( 0 ー ass TestCl ass { public,• char mHOge [ 128 Anima Ⅱ ) ( cout くく ' 土 m ー st セ第 public: Animal ( ) { co 乢 "Animal—End%n"; 〃← vi て tu 引でない ! void Set(const char* 土 n で ) : :strncpy(mHoge,inT,sizeof(mHoge)-I); わ virtual void Bark( ) 0 引 const char* Ge 日マ 0 土 d ) eons セ (xeturn mHoge; リ 0 ね“ Dog : P は 0 Animal ( st 土 0 void TestMain ( void ) public: Dog( ) ( co 此くくを Dog ー sta てセ第 } 咢 TestClass* theT -Dog( ) { cout くく "Dog—End*n"; void Bark( ) { cout くく OW ! wow!%nn; } す theT = static—cast<TestClass*>( t:maIloc(Sizeof(TestClass))) ー theT->Set()* test * ” CO くく theT->Get( ) くく end •:free(theT); デストラクタを v 減 ua にしない 深刻度☆☆☆ [ 症状 ] List List 20 AnimaI* Create—AnimaI ( 土 n ヒ inl) switch(inl){ case 1 : return new Dog; List delete ロでなく de 厄 te を使った static マ 0 土 d TestMain (void) TestClass* theT; theT ま new TestClass[81; de 厄 0 theT; static void TestMain(void) ~ AnimaI* theAnimal ま Create-AnimaI ( 1 theAnimaI->Bark( del ete theAnimal; 24 C MAGAZINE 2000 7