U. - みる会図書館


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

1. 月刊 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

2. 月刊 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

3. 月刊 C MAGAZINE 2000年7月号

十五ロ いないためです。 側できちんと解放しないと , メモリリーク ば bad ー a Ⅱ oc が投げられた状況なのに , そこ [ 対策 / 予防 ] の要因となります。さらに , 動的発生した で new を使い , 再度 bad ー a Ⅱ oc を発生させて 山 row するものはオプジェクトそのもの ものと静的な例外オプジェクトへのポイン しまうケースなど ) があるのを知らないせ か , オプジェクトのリファレンスだけにし タが混合している場合は , 後者を間違って いです。 ます。実際の話 , ポインタを thr 。 w しない 解放しようとしてハマるケースも起こりえ [ 対策 / 予防 ] と困る場面というのは , まずありません。 ます。 bad-alloc のようなケースでは二重に例外 [ 例外 ] つまり , どうころんでもポインタを throw が起こらないように , 例外クラスではメン なし。 するメリットはありません。ポインタにし バ変数はあらかじめ固定領域で確保してお [ 備考 ] たほうがコストが低いという妙な反論もあ き , new などで動的に確保をしないでおき 中程度の深刻度にしたのは確率的に低い りますが , それだったらリファレンスを ます。ほかのケースについても , この処理 からです。頻繁に発生し , しかもプログラ throw すればいいだけの話です。 をすると再度例外を起こさないかを検討し , ムを継続して長時間にわたって利用される 再び例外が起こらないような対策をとって ケースでは問題になるかもしれませんが。 ください。 例外クラスのなかで例外が起こりうる 通常 , 例外を発生するときは [ 例外 ] 深刻度☆☆☆ 物 row 例外クラス ( ) なし。 というような書式で , 直接例外を投げるか , [ 症状 ] [ 備考 ] あるいはあらかじめどこかに例外オプジェ 二重例外でプログラムがコケてしまう。 これはプレーキが壊れたときのための非 クトを作っておき , それのリファレンスを 結果的に例外のハンドリングがまともにで 常プレーキが , さらに壊れてしまうという 投げるべきでしよう。 きず , その弊害で悩まされます。また移植 情けない状況のことです。こうなってしま 例外オプジェクトを動的発生させてその 性や安定性に欠けます。 うと , もともとの例外をうまく伝えられず , ポインタを投げる方式は , メモリが不足し [ 原因 ] 泣きつつらに蜂の状態です。また , 例外が ている状況では bad ー a Ⅱ。 c 例外を併発させて 例外が起きるような状況では , 注意しな 連続的に起こり , 一種の無限ループ状態に しまう問題も出てきます。また , catch した いと , さらに例外が起きる可能性 ( たとえ 陥ることもありえます。 は , 他人にかっこよさを見せつけたいだけ のことだったのか , と考えるからです。 C + + のような複雑な混成言語を使う理由 は , いろいろと考えられると思います。人 によってさまざまでしようが , 筆者の場合 は , 現時点で ( いろいろな意味で ) もっとも 強力なプログラム言語である 実は筆者が C + + だけでなくオプジェクト 語としての C + + を導入するのかでもいいの というのが最大の理由です。何しろ , たい 指向の話題を取りあげるとき , 必ず気にす ですが ) という「目的」や「理由」を見失うと , ていのプラットホームで使えるという点で , るのが「理念と実装を混同していないか」と やたら苦労をするわりには報われず , ただ C 言語に次ぐほどの有利さを持っています。 いう点です。というのも , ある仕組みを希 ただしんどいだけだった , という結果にも また表現能力が豊富で , 筆者の好きな「ス 望すること ( 理念 ) と , その希望を実現する なりかねません。単に「かっこいいから」と ッキリした記述」を実現するのにもっとも いうばかまるだしの理由をあげる人もいま 楽な手段だからです。 ための手段 ( 実装 ) は混同すると話がややこ もちろん C + + よりも「スッキリした記述」 しくなるだけでなく , 本質を見失ってしま す。 います。また何のためにオプジェクト指向 筆者はプログラムの仕事で「かっこいい」 ができる言語が存在することも知っていま を導入するのか ( あるいはオプジェクト指 という言葉を使う人を , あまり信用する気 すが , 処理速度や開発環境の問題 , 言語そ 向を導入するつもりはないが , 改良版 c 言 のものの入手状況など , さまざまなことを にはなりません。なぜなら , この人の目的 特集 1 プログラミングの禁じ手 33 理念の誤解に関する禁じ手 新しい秩序を確立することほど難しい事業 はないーーーマキャベリ

4. 月刊 C MAGAZINE 2000年7月号

十五ロ ロ やエラーリカバリのプログラムが混じり , うのですが ) 。しかし税金は払っておくと れをリカバリする対策を考慮しているなら どこが本来の正常時の処理なのか , どこが あとでいいことがあるように ( 本当です よいという意見もあります。ただし , チー こんぜん 例外処理なのか渾然一体となって判別しに か ? : - ) ) , 例外をうまく活用できるように ム開発などでほかのメンバのスキルが怪し くくて , 結果的にプログラムの保守性を落 なると , もはや例外を使わないプログラム そうな場合は , なるべく避けたほうが吉だ とし , バージョンアップ時に間違ってバグ はイヤ気がさしてくるでしよう。うまく活 と思います。 を盛り込んでしまう事故を , ある程度減ら 用すれば , スッキリしたコーディング , 結 [ 備考 ] すことができます。しかし , こんな便利な 果的にバグの少ない保守性に優れたプログ コンストラクタで例外を起こしたときの 例外ですがやはり問題点があり , 便利な手 ラム作りができます。 最大の問題は , この状況ではインスタンス 抜きの手段と勘違いすると , とたんにしっ がきちんとできていないということで , デ ペ返しを食らうこともあります。 ストラクタが起動しない点です。インスタ コンストラクタ内で例外を起こす まず最大の問題点は例外は発生したとき ンスそのものができていないのですから , c 深刻度☆☆☆ に「投げる」という動作を起こしますが , atch してから delete でむりやり解放というこ [ 症状 ] のことは , ともできません。たとえば , List34 のプロ ①例外は関数の外に投げることができる。 コンストラクタで処理が完結せず , デス グラムでは mBu Ⅱ 1 が無事に確保できても , つまり , どこから例外が投げられたの トラクタが呼ばれないため , 資源の解放し mBuff2 を確保しようとして bad_alloc を引き か判別するのに苦しむ 忘れやメモリリークを引き起こし , その結 起こした場合 , デストラクタが呼ばれない ②再開をしたい場合 , どこから再開すべ 果で引き起こされるバグに悩まされます。 ことを確認できます。そうなると , mBu 幵 erl きなのか判断に苦しむ [ 原因 ] で確保したはずの SM 用工 SIZE ぶんの領域 ③例外を起こした後の処理がほったらか コンストラクタ内の例外の問題点を把握 はメモリリークとなります。デストラクタ しになるので , 不定状態や解放忘れの していないためです。 で何らかの後始末処理を行っていた場合 事故を誘発しやすい [ 対策 / 予防 ] は , 後始末が行われず , 支障が出てくるこ という問題を引き起こします。 コンストラクタ内では , いっさい例外を とも予想できるでしよう。 とくに①は深刻で , せつかくプログラム 起こさないようにするか , 例外が起きても とりあえず , このデストラクタを呼ばな を構造化して細かい部分まで調べなくても 解放忘れの弊害が起こらないような対策を い現象を押さえるには , 大局的にとらえられるようにした努力が水 ①コンストラクタ内で , try-catch を記述 とるとよいでしよう。 の泡になる可能性があります。どこがまず [ 例外 ] し , 例外がコンストラクタの外に投げ くて例外を起こしたのかを細かいレベルま コンストラクタ内で例外が起きても , そ られないようにする で追わないといけません。また , ②の特徴 ②コンストラクタ内で例外が起きる記述 コンストラクタ内で例外 により , 信頼性を要求されるもの , 継続的 はすべて禁止し , 別途 , 初期化関数を #define SMALL—SIZE 1024 に運転することを期待されるものに適用す 用意する #define LARGE—SIZE 1024 * 1024 * 1024 る場合 , 慎重に検討をしないとまずいでし というのが考えられるでしよう。ただ , ① class BadClass { char* mBuff1; よう。③はいうまでもなく , 例外が起きる の場合 , 例外が起きたということは中途半 char* mBuff2; public: 端なオプジェクトになっているので , これ たびに , よりひどい状況にハマったり , 再 BadCIass() { *C start*%nn cout くく 度例外を引き起こしかねません。 mBuff1 = new char[SMALL—SIZEl; に対処する必要があります。ひとつのアイ "mBuff1 allocated. 基n” cout くく mBuff2 new char[LARGE—SIZEl; とはいうものの怖いから例外を使うのは デアとして , 工ラーインジケータ代わりの くく メンバ変数を用意して , 例外が発生したな イヤだといっても , 最新の C + + の仕様では -BadCIass( ) { cout くく D*D start*%nn ・ ら , それを書き換え , あとから例外が起き 例外を使わざるをえませんし , 付属のライ delete[ ] mBuff1; delete[ ] mBuff2; たことを判断する方法が考えられるでしょ プラリが例外を使う前提で組まれているこ "*D end*%n" COUt くく う。また , 例外が投げられた以降の処理を とが多いのでイヤだけど避けようがないと 実行していないので , そこでメンノヾ変数の いう , まるで税金みたいな話になっていま 初期化を行っていた場合は , メンバ変数が す : - ) ( コンパイラの設定によって , 例外を 不定値になり , これも別の支障を引き起こ 使わないようにできるものもあります。た だし , その場合でも , 例外があることを前 す可能性があります。 ②は初期化関数を別途用意するという手 提にした付属ライプラリの問題がつきまと 特集 1 プログラミングの禁じ手 31 List 34 static void TestMain ( void ) theB;

5. 月刊 C MAGAZINE 2000年7月号

違いはないですが : - ) ) , 基底クラスの場合 は「親クラスこけたら , みなコケた」で , 継 承しているクラスすべてにわたって修理と 検証が , もちろん継承したクラスを利用し ているコードにも修理と検証が必要になり , きわめて被害が大きいわけです。 誰が確保 , 解放するのか不明確な ポインタメンバ変数を持つ 深刻度☆☆☆ [ 症状 ] 不定値のポインタによってメモリ破壊や メモリリークを引き起こしやすくなり , し かも原因が特定しにくく , バグを取るのに 苦労します。最初はうまくいっても , プロ グラムの変更でバグを誘発しやすくなりま す。 ポインタの確保と解放に関する挙動をど うするか深く考えずにプログラムを作って いたなど。 [ 対策 / 予防 ] コンストラクタでポインタメンバ変数を NULL に初期化するか , 最初から意味のあ る値をセットしておくとよいでしよう。ま た確保と解放はクラスの外部ではなく , ク ラスの内部の責任とし , デストラクタで確 実に解放するようにしましよう。 [ 例外 ] なし。 [ 備考 ] ポインタ関連のバグはあい変わらず C + + でもおなじみのものです。ただし C + + では , メンバ変数がポインタであったとしても , [ 原因 ] コンストラクタで不定値でなくするように し , デストラクタで明確に解放するという , ちょっとした注意を払うだけで , わりあい 簡単に対処できるはずです ( が , そうアド バイスしても , できないと頭をかかえる人 はいるんですがね : - p ) ) 。 new が失敗しうることを考慮しない 深刻度☆☆ [ 症状 ] メモリ不足な状況でのプログラムの挙動 が保証されません。 [ 原因 ] new が失敗する場合があることを知らな いか , 失敗したときのことをあまり考えて いません。 [ 対策 / 予防 ] new が失敗した場合は bad ー a Ⅱ oc が投げら れるので , この catch ハンドラを必ず持たせ るか , bad-alloc が投げられる状況は異常事 態で継続不可能であると判断し , そこでプ ログラムを停止するなどの対策を施しまし よう。 [ 例外 ] new で失敗時は強制的に終了するという 前提で組む場合 , あるいはそもそも失敗し ようもない広大なメモリが保証されている 場合は , ひょっとしたらいいのかもしれま せん。しかし本当に本当にそれでいいのか 自問してから試してください。 [ 備考 ] 仮想記憶のサポートがあり , 実質 , 広大 なメモリ空間があるつもりの環境では , わ 例外に関する禁じ手 誰かが助け起こしてくれるのを期待してわざ と倒れても , 誰も助けはしないマキャベリ 30 C MAGAZINE 2000 7 りあいに new が失敗するということを考え ないプログラムはあるようです。また最新 の規格では new が失敗する場合 , bad_alloc の例外が起きるので例外ハンドラでサポー トすればいい , あるいはそういう状況だっ たら , とっととプログラムを落としてしま え , という考えもあるようです。しかし本 当にそれでいいのか悪いのかは筆者にはわ かりません。ただし商用で使うもの , 信頼 性を強く要求するものでは , こういう考え は嫌われることが多いでしよう ( そもそも 信頼性を要求するものに C + + を使うのは危 険という意見はさておき :-P) ) 。 メンバ変数が不定値で評価される [ 原因 ] 植性や安定性に欠けます。 わけのわからないバグに悩まされる。移 [ 症状 ] 深刻度☆☆☆ 例外を使えば , 従来のようなエラー検出 り導入が可能です。 向のプログラムで作ったとしても , すんな のない考えなので , 従来どおりの手続き指 そのものはオプジェクト指向とは直接関係 入された考えです。ありがたいことに例外 例外というのは C 言語にはなく , C + + で導 繰り返さないでおきましよう。 定値の恐怖はさんざん述べたので , あえて 変数の不定値を使う」のパターンです。不 これは禁じ手の C 言語編でも述べた「自動 [ 備考 ] なし。 [ 例外 ] するとよいでしよう。 変数をあらかじめコンストラクタで初期化 放っておくと不定値になりそうなメンバ [ 対策 / 予防 ] さをわかっていません。 ケアレスミス。あるいは不定値の恐ろし

6. 月刊 C MAGAZINE 2000年7月号

十五ロ ◆の ロ 横車を通そうと思えばできなくはありませ [ 例外 ] もないでしよう。 ん。しかし筆者は , こんな人がいるプロジ なし。 すでに広く利用されている ェクトで一緒に仕事をしたいとは思いませ [ 備考 ] 基底クラスの仕様を変える んし , ほかの人たちだって , そうじゃない C + + になって , これは便利と思われ , な 深刻度☆☆☆ かには偏愛する人までいるのが「演算子オ でしようか。 ーバロード」です。たしかに複素数の計算 [ 症状 ] などのように , 本来の言語仕様に備わって チームで開発している場合 , ほかのメン いない新しい型で計算式を表現する場合 , バから苦情が続出したり喧嘩の原因になり とても便利です。まさか , List 32 のような ます。 表記がうれしいというマゾヒストは少ない [ 原因 ] プログラムにバグがあっても簡単に見つ のではないかと思います。 チーム開発をする場合 , ある部分の影響 けにくくなります。 しかしなかには , どう考えても必然性が がほかに影響を及ばし , とくにべースとな ないのにむりやり使っている例や , 完全に るクラスを勝手に仕様変更した場合 , その 自分が賢いことを証明できると勘違いし 意味を間違って使っている例もあります。 仕様に依存しているほかのメンノヾに被害を ているか , 演算子オーバロードに対する過 たとえば List 33 のようなちょっとわざとら 及ぼすことをわかっていないせいです。 剰なフェチシズムがあるのでしようか しい例ですが , 明らかに加算の考えが間違 [ 対策 / 予防 ] [ 対策 / 予防 ] っています。ところが提供されたライプラ あなたひとりが仕事をしているわけでな 必然性のない演算子オーバロードはなる リを利用するだけの立場の人は , まさか自 いことをわきまえましよう。 べく避けるようにしましよう。実際の話 , 分たちが使うライプラリのなかで , とんで [ 例外 ] どうしても演算子オーバロードをしないと もないコーディングがなされているとは夢 個人でプログラムし , 全責任を負うつも ダメだという場面はめったなことではあり にも思わず , 懸命に自分たちのソースの間 りなら許されるかもしれません。ただし , 違いを探すハメになるわけです。 ません。 自分で自分を呪いたくなるような場面が出 この種のトラブルは演算子オーバロード てくることは覚悟してください。 演算子オーバロードを使わない例 だけではなく普通の関数でもありえる話で [ 備考 ] すが , その場合ひょっとしたら悪いのは自 仕様変更を勝手にされて , 混乱とトラブ 分たちじゃないのかもと気づきやすいのに ルに陥るのは C + + だけの話ではなく , 大昔 対し , 演算子オーバロードの場合は , 気づ からあることです。しかし従来の言語では くのに時間がかかるという点で , より凶悪 被害というのは , 変更されたひとつの手続 です。もちろん , こういうのはチーム開発 き ( あるいは関数 ) をどこで使ったかを追い をしていた場合は , チーム全体が気まずく かけ , そこをせっせと修理するだけで済む のに対し ( だとしても文句プープーなのに なったり , 喧嘩のネタになるのはいうまで 演算子オーバロードを悪用する 深刻度☆☆☆ [ 症状 ] [ 原因 ] 0 List 32 void 「 ( ) ー NewClass theA,theB,theC,theD; 一、 7/theD = theA 十 theB thec; のつもり↓ theD. Assign(theA); NewCIass theTemp; theTemp. Assign(theB); theTemp. Multiply(theC) 引 theD. Add(theTemp); 演算子オーバロードで混乱を起こす例 0 ね ss MyInt ( 土 n し mlnt; public: I 北 ( ) ( mlnt = MyInt(const MyInt& inl) ( mlnt 号土れ工 . ml れ I 北 ( 土 n し加工 ) { mlnt 土 n 島 土 n し GetVaI( ) cons し { return mlnt; MyInt& operator=(const MYInt& 土れ I ) ( mlnt = inl. mlnt; return * セ h 土町 MyInt& 叩 e て a セ 0 て = ( セ inl){ mlnt 土 n 島 return *this; List in line ostream& operator< く ( ostream& 土 OS , cons セ MyInt& inl ) ioS くく inl. GetVal( teturn 土 OS ー static void TestMain(void) MyInt theA,theB,theC; theA = 1 ー theB = 2 ー theC = theA 十 theB; cout くく "theA 十 theB 咢 くく theC << endl; inline My 工れし operator 十 (MyInt inA,MyInt 土ロ B ) 町 I the崩8()心 . GetVal() ー土 . Getv 引 ( ) / / ←これは何だ ? return theAns ー 特集 1 プログラミングの禁じ手 29

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月号

特集 1 プログラミン % 禁じ手一 語編 りとよく使われます。つまり , List 23 のよう なコードです。こんなことをすると「 delete NULL ; 」になってマズいのではと心配しなく ても大丈夫です。というのも C + + の規格で は「 0 に対して delete を適用しても , 何も起 こらない」 ( 「プログラミング言語 C + + 第 3 版』 の 6.2.6 「自由記憶領域の確保」を参照のこ と ) ことになっているからです。 不定値で de te する 深刻度☆☆☆ [ 症状 ] 移植性が低く , 不可解なバグに悩まされ ます。 ポインタが不定値のままで評価されるよ うなプログラムになっています。 [ 対策 / 予防 ] コンストラクタ内であらかじめ NULL で 初期化するなど , 不定値を引き起こさない 工夫を導入しましよう。 [ 例外 ] なし。 [ 備考 ] これは禁じ手の C 言語編で紹介した「自動 変数の不定値を使う ( 2000 年 4 月号 29 ペー ジ ) 」の変形です。不定値を扱った場合のプ ログラムの挙動は残念ながら C + + でも健在 です。また効率を追求する C 言語の伝統を 守ったゆえか , クラスのメンバ変数もコン ストラクタがきちんと作動して初期化する クラスのオプジェクトだったらいいのです が , そうでない場合 , やはり不定値です。 たとえば , List24 のプログラムは確実に 不定値の delete を引き起こす例です。とい うのも Set メンノヾ関数では「 deletemText 」と なっていますが , mText は不定値のままな [ 原因 ] List ので ( デフォルトコンストラクタのなかで 「 mText = NULL ; 」を書いていないので ) 問題 があります。ちなみに gcc でコンパイルした 実行プログラムは Coredump をしました。 継承したクラスのコンストラクタが 基底クラスのどのコンストラクタを 呼ぶかを確認しない 深刻度☆☆☆ [ 症状 ] 期待したとおりに動きません。 [ 原因 ] コンストラクタの挙動に関する勉強不足 です。 [ 対策 / 予防 ] 勉強しなさい : - ) 。 [ 例外 ] なし。 [ 備考 ] List 25 のようなコードで発覚する落とし 穴です。 「 ExTestCIass::ExTestClass (const char * inT ) 」を呼んだのだから自動的に基底クラ スの「 TestClass::TestClass (const char * ⅲ T) 」を呼ぶのだろう , と勝手に解釈したた めに起きた不都合です。 この例では基底クラスのデフォルトコン ストラクタが呼ばれるため , 結果的に mText は NULL にされます。したがって , 期待ど おりの挙動をさせたいなら , List26 のよう に基底クラスのどのコンストラクタを呼び 出すべきかを明示しないといけません。 違うクラスのメンバをキャストで 自分の予期したとおりにプログラムが動 [ 症状 ] 深刻度☆☆☆ むりやり使う 基底クラスのどのコンストラクタを呼び出すべきか明示する class ExTestClass : public TestCIass { ExTestClass(const char* inT) : TestCIass(inT) { mLen = : :strlen(inT); 26 いてくれません。 [ 原因 ] 特集 1 プログラミングの禁じ手 27 ん。リファレンスはポインタのことだと勘 われることが望ましくないことを知りませ リファレンスとして , ヌルや不定値を使 [ 原因 ] 移植性が低く , 不可解なバグに悩まされ [ 症状 ] 深刻度☆☆☆ 作ってしまう ヌル値や不定値のリファレンスを になるのは変わりありません。 け道として利用すると , とんでもない結果 っかり状況を認識せずに , 安直で便利な抜 同様 , C + + でもキャストはプログラマがし は保証できません。結局のところ C 言語と スがあった場合 , どのような挙動になるか Class にのみ存在するメンバ変数へのアクセ かない場合もあります。たとえば , Exnest この方法がうまくいく場合もあれば , い ます。 めにむりやりキャストをかけて利用してい estClass クラスにない Tou 叩 er を使いたいた TestCIass , Ex2TestCIass での話です。 ExT この例は , ともに TestCIass を継承する Ex 思って間違いはありません。 ぶのは「バケツでウラン」症候群のひとっと 異の技です。もちろん , これで動いたと喜 List 27 のようなプログラムで発覚する驚 [ 備考 ] しよう。 利用して安全に使える工夫を導入すべきで ただ , その場合でも dynamic_cast や R'IYI 、 I を ている場合はかまわないかもしれません。 キャストを前提とした使われ方を想定し [ 例外 ] 勉強しましよう : - ) 。 キャストに関する弊害の認識不足です。 [ 対策 / 予防 ] います。 ゴマカシが C + + でも通用すると勘違いして OJT 的感性。 C 言語でのキャストによる

9. 月刊 C MAGAZINE 2000年7月号

[ 原因 ] にわかりますが , コンストラクタが 8 回起 で問題が発覚しても , こんな変なクラスを 動するのに対し , デストラクタは 1 回しか コピーコンストラクタと = 演算子を使っ 作ったやつが悪いのだ , にされるからです。 起動しません。つまり 7 個ぶんのオプジェ たプログラムは同じだと勘違いしているた あらかじめトラブルを避ける意味で「使わ クトが解放せずに残り , メモリリークを引 れたくない暗黙のメンバ関数を未定義にす め。 き起こすわけです。正しくは [ 対策 / 予防 ] る」のもひとつの策でしよう。 「 delete [ ] theT; 」 勉強しなさい : - ) 。 [ 例外 ] と記述しないといけません。 ニ重に de te する なし。 深刻度☆☆☆ コピーコンストラクタと [ 備考 ] 代入演算子を混同する List 22 のようなプログラムで (A) と (B) [ 症状 ] 深刻度☆☆☆ が , まったく同じ挙動だと勘違いしてはい 移植性が低く , 不可解なバグに悩まされ [ 症状 ] けません。というのも , 「 TestClass theB = t ます。 プログラムが期待したとおりに動きませ heA; 」と WTestClass theB; 」と「 theB = theA; 」 [ 原因 ] の合成ではないからです。つまり「 TestClas 解放に関する考えが厳密でないとか , s 市 eB ( theA ) ; 」と同じでコピーコンストラ 重に delete することが悪いということを知 = が書いてあっても代入演算子とはかきらない例 クタが起動します。これは自分で実際に らないためです。 Tes ℃ lass に表示ルーチンを仕込んで確認す [ 対策 / 予防 ] れば簡単にわかります。 二重解放を防止するための工夫の導入 これは個人で開発する場合は自分で無意 や , 二重 delete が悪いことを勉強してくだ 識のうちに避けて通り , 結果的に弊害に出 さい。 会わない率が高いかもしれませんが , 誰が [ 例外 ] 参加するのかわからないチーム開発では致 なし。 命傷になりかねません。なぜなら他人は , [ 備考 ] コピーコンストラクタと代入演算子の混同 これも OJT 的感性で起きる現象で , 「バケ がどうのこうのという心配よりも , 自分の ツでウラン」症候群のひとつです : - ) 。二重 保身や納期を守るほうが大事だから , いち delete を防ぐために delete した変数に強制的 いち気にしなかったり注意を払わず , あと に NULL を代入しておくという方法は , わ 基底クラスのメンバ変数がうまく設定できない例 clasg TestCIass ( char* mText ー public: TestCIass( ) { mText = NULL; TestCIass(const char* inT) { mText 号 strdup(inT); 嵭 virtual -TestCIass( ) { delete mText; const char* Get( ) const ( return inText; わ class ExTestCIass : public TestClass { int mLen; public: ExTestClass( ) { mLen = 0 ・ ExTestCIass(const char* 土れ T ) ( mLen = 。 : :strIen(inT); 土 n し GetLen( ) const { return mLen;• ん。 List 22 TestClass theA(*TEST"); TestClass theB ま theA; nestc lass theA ("TEST" リ TestClass theB; theB ま theA; List 23 ニ重 del ete 防止策 delete mptr; mPtr = NULL; List 24 List 不定値で d ete が起きる例 cl ass TestC ね ss { char* mText; public: TestCIass( ) { TestClass(const char* inT) ( mText strdup(inT); -TestCIass( ) { delete mText; void Set(const char* inT) { delete mText; mText = strdup(inT) デ const char* Get( ) cons に ( return mText; 冖 . ( 中略 ) を .. static void TestMain (void ) TestC lass theT2 00000.00 、 000 : cout くく theT2. Get( ) くく endl; static void TestMain ( VOid ) ExTestClass theT("TEST"); cout くく theT. Get() くく d 嵭 / / ←セットしたはずの” TEST ″が表示きれない 26 C MAGAZINE 2000 7

10. 月刊 C MAGAZINE 2000年7月号

特集 1 [ 対策 / 予防 ] プログラミン % 禁じ手 言語編 となっていて , AnimaI クラスのデストラク タが呼ばれているのに Dog クラスのデスト ラクタが呼ばれていないのが確認できます。 Dog クラスのデストラクタ内で解放処理が あったとしても , それがきちんとなされず 結果的に資源の解放忘れやメモリリークを 引き起こすわけです。 この種のバグはなかなか発覚せず , 症状 を確認できたとしても , その原因や場所を 特定しにくい , やっかいなものになるのは いうまでもありません。 ところで , 仮想関数を避ける人たちの理 由は , ①仮想関数というのがよくわからない。 仮想関数恐怖症 ②仮想関数を選択した場合のコストが気 になる。メモリをよぶんに使う , 実行 速度も落ちる というのが考えられます。このうち①は感 情の問題なので置いておくとして ( チーム 開発をする場合 , リーダが恐怖症ゆえ「仮 想関数使用禁止令」を発することがあるか もしれません。その場合は困ったものです が , 筆者にはどうすればいいかのアドバイ スはできません ) , ②の場合ははたしてそ のコストが避けないといけないものなのか , あるいは必要以上に過敏になっているので はないかを調べるべきでしよう ( そもそも C + + を選択した時点で , C + + は C 言語よりも ( いろんな意味で ) コストが高いことを知っ ておくべきだったのに :-P) 。案外 , 気にし すぎだったということが多いのです。 コンストラクタやデストラクタが 起動する順番があいまい 深刻度☆☆☆ [ 症状 ] 移植性が低い。プログラムをいじるたび に症状が変わったり , 再現性の低いバグに 悩まされます。 [ 原因 ] 外部変数のオプジェクトのコンストラク タ / デストラクタの起動順番があいまいで あることがわかっていません。 外部変数のオプジェクトを減らす , ある いはポインタにして , 明示的にコンストラ クタとデストラクタを呼ぶプログラムにし [ 備考 ] なし。 [ 例外 ] ます。 ることがわかっていません。 new と malloc は (free と delete は ) 別物であ [ 原因 ] ません。 それ以前に , まともにプログラムが動作し たり , 再現性の低いバグに悩まされます。 プログラムをいじるたびに症状が変わっ 移植性が低い。 [ 症状 ] 深刻度☆☆☆ ( f 「 ee と de te を混同する ) new と ma c を混同する す。 起動する順番を明確にする工夫が必要で ポイントするなどして , コンストラクタが たり , m 凾 n 関数で自動変数を作ってそれを main 関数の先頭で明示的に new で発生させ めるか , あるいはポインタにしてしまい , ひとつのソースにすべての外部変数をかた ります ) 。そのため外部変数を作る場合 , しているクラスで支障が起こるケースもあ の頭で初期化処理をしていることを前提に コンストラクタが起動するため , main 関数 クトを作った場合 , main 関数が始まる前に この問題とは別に , 外部変数でオプジェ 照のこと。 10.4.9 「非局所的な静的オプジェクト」を参 ません ( 『プログラミング言語 C + + 第 3 版』の コンストラクタが起動するのかは保証でき ースに分散して記述した場合はどの順番で コンストラクタが起動しますが , 複数のソ 変数を並べて記述した場合は並べた順番に というのも , ひとつのソースのなかで外部 ハマり込む事例が多いのが , この項目です。 外部変数でオプジェクトを作った場合に [ 対策 / 予防 ] 勉強しなさい : - ) 。 [ 例外 ] なし。 [ 備考 ] 特集 1 プログラミングの禁じ手 25 タに表示ルーチンを入れて確認すれば簡単 TestCIass のコンストラクタとデストラク があります。 たとえば List 21 のようなプログラムは問題 的に用意した落とし穴だとしか思えません。 か筆者には , 効率を追求するために確信犯 これも C + + のダークサイドです。という [ 備考 ] なし。 [ 例外 ] 勉強しなさい : - ) 。 [ 対策 / 予防 ] であることがわかっていません。 new と newC] は (delete と delete[] は ) 別物 [ 原因 ] こともあります。 以前に , まともにプログラムが動作しない の結果で起きるバグに悩まされます。それ メモリ破壊 , 資源の解放忘れ現象や , そ [ 症状 ] 深刻度☆☆☆ (delete と delete 口を取り違える ) new と new[] を取り違える 候群であるのはいうまでもないことです。 やめましよう。これも「バケツでウラン」症 動けば OK ではないかという OJT 的感性は ど ) も必要です。 るための準備作業 ( デストラクタの起動な 解放だけでなく , オプジェクトを消滅させ す。もちろん解放する場合には , メモリの 作業 ( コンストラクタの起動など ) が必要で 以外にオプジェクトとして動くための準備 ジェクトを確保する場合には , メモリ確保 メモリ確保と解放の関数です。実際にオプ あるのですが・・・・・・ ) , ma Ⅱ oc と仕 ee は単なる ( また困ったことに動いてしまう場合も まともに動くと勘違いしている人がいます どういうわけか List 20 のプログラムで ,