C プログラマのための C + + 入門 実践 0 + + ゼミナール クラスの使われ方によっては定義②のよ 行目のエラーは , 「派生クラスから基本ク うに左辺値になれる型変換を提供する場合 ラスへの代入はできるが , 基本クラスは p ublic として継承されていないと隠蔽され も考えられるでしよう。あたりまえですが , 型変換の叩 erator は戻り型を指定できませ ているから代入できない」のでエラーにな ん。ある型への変換を提供するので最初か っているのです。 18 行目のエラーは本当に ら返す型は変換先の型に決まっているから 工ラーで , 「 class Base から class Derive へ の変換は定義されていない」のでどうしよ です。ただ演算子のオーバロードが一貫し た形式で定義できない形になっていてわか うもないといっています りにくくなっているのは否めません。この 18 行目のエラーでわかることは , コン パイラは派生クラスから基本クラスへの代 例でもいささか冗長なのは承知ですが , int operator int (void) { 入 , つまり変換は定義しなくても暗黙に知 fprintf (stderr , っていることになります。なのでプログラ int& operator int (void) { マは派生クラスから基本クラスへの変換は . } / / 定義 ( 2 ) fprintf (stderr , 定義しなくてもいいのです。「しなくてい い」のと「してはいけない」とは違いますが と記述できたほうが一貫していると思うの は筆者のワガママでしようか ( このように しなくてもいいのは確実です。 18 行目の 工ラーは , 変換の定義は必須なのでこれは 記述はできません。念のため ) 。蛇足です が定義 ( 1 ) と定義②では , GCC では実行 自明といえばそれまでです。 List 5 がコン パイルできる代入の一例です。コンパイラ 結果が同じだけでなく List3 の実行部分は は変数 vl の class Base 部分のメモリを v0 に 完全に同一のコードを生成していました。 コピーするコードを生成していました。最 クラスの継承と変換 初に型変換の話に脱線したのは 18 行目の 型変換の話を再度したのは , 基底クラス 工ラーメッセージの意味を理解するために と派生クラス同士の代入の関係を明確にす 必要だったからです。 るためです。 C + + のクラスは C の構造体の クラスへのポインタ 進化なので , 基本的に同じクラス ( 構造体 ) C でも C + + でも , クラス ( 構造体 ) 同士の 同士は代入できます。構造体の代入は , 内 部的には純粋なメモリ間の単純コピーです。 直接の代入はサポートされていてもほとん ど使うことはないと書きました。 C では構 それゆえオーバヘッドも大きく , ANSI C 造体はほとんどの場合ポインタを用いてア より前の古い C では構造体の代入は認めら クセスします ( コラム 2 ) 。 C の最大最強の れていませんし , 現在でも構造体をコピー 武器はポインタで , 最悪のバグがポインタ するようなコードはあまり書かれません。 の操作にかかわるバグです。 C + + は C のポ ですが , 言語の仕様を学ぶ観点から , C + + インタの機能を 100 % 受け継ぐ言語で , C でのクラス間の代入を知っておくことは重 のポインタ操作でできることは , 基本的に 要です。今回は少々手法を変えて「トライ & 工ラー」で C + + の機能をあぶり出してみ すべて C + + でもできます。ただし C + + では たいと思います。まずは List4 です。 ポインタ同士の変換に厳しい制限があって , 完全に行われることを前提としているプロ C では警告 ( 工ラーにするコンパイラもあ 結論から先に書きます。 17 行目は「継承 グラムをコンパイルするのは , ソース変更 りますが ) だったキャストなしの異なった 時に隠蔽されているので代入できない」と タイプのポインタの代入は , キャストなし なしには不可能になっています。これは A 18 行目は「 class Base か いう意味のエラー NSIC でも同様なので , とにかく乱暴で強 ら class De ⅱ ve への変換は定義されていない」 では原則として許されず , すべてエラーに 力だった C のポインタの概念をかなりおと なります。このために C + + ではかなり昔の という意味のエラーになります。このエラ なしくさせています。ただ , C および C + + C のように sizeof ( int) = = sizeof ( int * ) で un ーメッセージから C + + がこのプログラムを はポインタなしではその力を発揮できない signed int および int と int * 間の相互代入が どのように解釈したかがわかります。 17 クラス間の代入 ( 工ラー ) 1 訂 0 lasg Base 土 n し baseVar; 3 : 5 : 6 : Class Derive : Base 7 : { 8 : 土武 DeriveVar; 10 : ーー 11 : Base v0 12 : Derive vl 13 : VOåd 14 : 15 : test (voåd) 16 : { v0 vl にフ / 立 ro て ! ! 17 : vl VO; / み Error!V 18 ま 19 : 1 0 クラス間の代入 0 ー ass Base int baseVar; class Derive : publåc Base 土 n し DeriveVar ー Base 。 VO; Derive vl; void test (void) 代入テストのポインタ版① C ー ass Base int basevar; class Derive Base し DeriveVar ー Base * v の」 Derive * vl 引 void (void) test / / て 0 てい ! v0 = vl vl = ⅶーー〃て 0 て ! ! ! 実践 c + + ゼミナール / 1
無名内部クラス ところで , List6 のリスナである Ac ⅱ onH andler 型のオプジェクトはプログラム中で 1 回しか使われません。まあ 1 回しか使わ れないといったら , 当連載のサンプルプロ グラムのクラスはほとんどそうなんですが 実際のプログラムの中でも , リスナの多く は 1 回しか使われないものです。 1 回しか 使われないクラスに名前を付けるのはめん どうです。 Java はクラスの中でクラスの宣言をする ことが可能です。クラス中のクラスを内部 クラスといいます。さらに 1 回しか使わな いクラスは , クラス名を与えないで宣言す ることができます。これを無名内部クラス ( 匿名内部クラス ) といいます。 まず内部クラス。これは原理は単純で , クラスの中でクラスを宣言すると内部クラ スになります。メソッドの中で宣言するこ とも可能です。 たとえば , List 6 のクラス ActionHandler をエデイタで切り取って , main メソッドの 中に持ってきたとしても , ちゃんと動作し ます。実行結果は示しませんが , やってみ てください。 このほか , メンバとしての内部クラスも あります。原理は単純ですが , 文法的には かなり複雑です。とにかく「メソッドの中 でもクラス宣言ができる」と覚えてくださ 1 回しか使わない内部クラスの場合 , ク ラス名を省略して無名内部クラスにするこ とができます。ⅱ st7 がそれです。 ちょっとわかりにくいので該当する部分 を抜き出しましよう。 pub lic void actionperformed ( ActionListener 引 = new ActionListener( ) { まだわかりにくいと思いますが , 2 つ目 の ActionListener の後の { } がない場合を想 像してください。 ActionListener 引 new ActionListener( このように普通のコンストラクタ呼び出 しになります。 ・・もっとも ActionListene r はインタフェイスなので , コンストラクタ 呼び出しはありえないのですが , ちょっと 無視しててください。 クラスかインタフェイスのコンストラク タ呼び出し ( ? ) の後にクラスの中身を記述 すると , 「そのクラスを継承した無名クラ ス」もしくは「そのインタフェイスを実装 した無名クラス」が宣言され , 同時にオプ ジェクトが生成されるのです。 だから ActionListener 型の変数 al に入る のは , ActionListener 型そのもののオプジ ェクトではなく ( インタフェイスだからあ たりまえですが ) , ActionListener を実装し た無名クラスのオプジェクトです。代入互換 性によって変数 al に代入できたわけです。 なお , ActionListener 型の変数を使わず に , コンポーネントに直接リスナオプジェ クトを登録してしまうやり方もあります ( そ のほうが多いかもしれない ) 。生成と登録 を同時に b. addActionListener(new ActionListener(){ public void actionperformed( ActionEvent e ) { のようにします。さすがにこうなるとやや こしいですけどね。この使い方は覚えてお いてください。 このような無名内部クラスを使ったプロ グラムをコンパイルすると , 記述したクラ スの class ファイルのほかにいくつか class フ ァイルができます。たとえば Iist7 をコンパ イルすると Sample7. class のほかに , Sampl e7$1. class と SampIe7$2. class というファイ ルができると思います。これらは無名内部 クラスの class ファイルであって , もちろ ん動作に不可欠です。うつかり消したり , システムに入れ忘れたりしないようにしま しよう。 アダブタ こまできてようやく , List 1 から「おま じない」としてきた部分の説明ができます (List 1- ① ) 。これは WindowListener の無名 内部クラスを宣言しているのです。 Windo wListener は , ウインドウに関するイベン ト , たとえば「最小化された」「通常の大き さに戻った」「閉じられた」などを担当する リスナであり , こでは , ウインドウが閉 じられたとき System. exit(0) を呼び出して プログラムを終了させています。 実はこれがなくてもコンパイルと実行自 体は可能なのですが , その場合終了後のコ ンソールが変になります ( とくに勧めませ んが , 興味があればやってみてください ) 。 ところでお気づきでしようか。リスナの 型名は WindowListener となっているのに 生成しているのは WindowAdapter を継承し た無名内部クラスです。この点でボタンの ActionListener とちょっと違っています こは WindowListener を実装した無名内 部クラスとしても問題ないのです。しかし , W1ndowIistener にはメソッドが 7 つあるので , 実装するには全部書かねばならず , すると , WindowListener ー = new WindowListener( ) { public void windowCIosing( WindowEvent e ) { System. exit( 0 / / 以下 , 必要ないメソッド public VOid windowActivated( WindowEvent e){ } public void windowClosed( WindowEvent e ) { } public void windowDeactivated( WindowEvent e ) { } public void windo&iconified( / 8 C MAGAZINE 2001 ActionEvent e ) { 11
す。導入として一度参照されてはいかがで しようか。 PlayWnd は SDK のインストール ティレクトリの・・・・・・ YmssdkYsamplesYMuIt imediaYDirectShowYPlayersYPlayWnd にあ ります。 フィルタの開発 DirectShow を使用したアプリケーション を開発していると , しだいに WindowsMe diaPlayer に含まれるフィルタ群だけでは 機能が足りないと感じてくると思います。 前述しましたが , DirectShow では機能ご とに分割されたモジュール , すなわちフィ ルタを組み合わせて自由にデータの流れ ( ストリーム ) を構築することができます。 こでは , 独自フィルタの作成方法に関し て説明します。 必要なインタフェイス フィルタ自身は COM オプジェクトとし て実装が行われており , 少なくとも以下の インタフェイスが必要となります。 ・旧 aseFiIter : フィルタグラフマネージ ャからの再生 , 停止要求を受けたり , 自身が持っているピンの管理を行う ・ IPin : 入力 , 出力に分類され , ピン同 士の接続 , テータの受け渡しなどを行 つ ・ IMediaSample : テータサンプルを管 理し , 次のフィルタへと渡される これらのインタフェイスを 1 から実装す るのはたいへんですが , SDK にはあらかじ め必要な機能を実装した基底クラスが用意 されていて , これらのクラスを派生させる ことで独自の機能を持ったフィルタを容易 に作成できます。フィルタを作成するため の基底クラスはその用途ごとにいくつか存 在し , これらは CBaseFilter クラスから派生 しています。以下にフィルタの基底クラス の種類を列挙します。 CBaseFilter フィルタとしての基本的な機能を実装し ています。ピンなどは CBasePin 派生クラス 1 38 C MAGAZINE 2001 11 を使用して , あらかじめ独自に実装する必 CTransformFilter 基底クラスです。 ソースフィルタを作成する際に使用する CSource 要があります。 1 . 指定されたソース ( ファイルなど ) に適 ます。 下の手順でフィルタグラフを構築していき する場合には , これらの情報を使用して以 を使用して自動的にフィルタグラフを構築 込みます。 IGraphBuiIder インタフェイス タとして必要な情報もレジストリ内に書き の COM として必要な情報以外に , フィル しかしながらこの関数の内部では , 通常 数を工クスポートしています。 なります。すなわち , DllRegisterServer 関 ンプロセス COM コンポーネントと同様に したが , システムへの登録方法も通常のイ ーネントとして実装されていると前述しま フィルタは , インプロセス COM コンポ 自動レンダリングの仕組み フィルタグラフ BasePin クラスなどもあります。 このほかにも , ピンを実装するための C ラスが用意されています。 を作成するために CBaseVideoRenderer ク 使用する基底クラスです。ビデオレンダラ レンダリングフィルタを作成する場合に CBaseRenderer あります。 底クラスとして CVideoTransformFilter が このほかに A Ⅵでのコンプレッサの基 ラスを使用します。 使用する場合には , CTransInPlaceFilter ク イルタから渡されたアロケータをそのまま ファ ( アロケータ ) を使用します。上流のフ ラスは , 入力ピンと出力ピンで異なるバッ 合に使用する基底クラスです。この基底ク トランスフォームフィルタを作成する場 合するソースフィルタの検索 外にフィルタとして必要な情報をレジスト キスポート関数は , COM としての情報以 フィルタの登録時に RegisterServer 工 適合するフィルタの検索 ができます。 も同じように独自フィルタを使用すること るようなので , Windows Media Player 上で も IGraphBui1der と同様の処理で動いてい きます。また , Windows Media Player 自身 irectShow を使用して再生させることがで クランプルをかけたメディアファイルを D 指定できるので , wave ファイルに独自のス 独自のソースフィルタを使用するように ig. 2- ② ) 。 字列を含む場合 , 以下のようになります ( F ッダ情報として 4 バイト目に "aaa' という文 たとえば , あるメディアファイル内にヘ (Fig. 2- ( 1 ) ) 。 イルタを決定するといったことも可能です み , その情報を元にして読み込むソースフ 部分に書かれているバイト情報を読み込 また , 拡張子以外にもファイル内の特定 することができます (Fig. 1 ) 。 拡張子から読み込むソースフィルタを指定 RenderFiIe メソッドが呼び出された場合 って説明をしていきます。 IGraphBuilder:: こでは , ファイルソースフィルタに絞 ソースフィルタの検索 していきましよう。 これらの各処理に関して少し詳しく説明 に対して 4 ~ 7 までの処理を繰り返す 7. 成功した場合は , 読み込んだフィルタ 6. 読み込んだフィルタとの接続を試みる 読み込む 5. 適合するものがある場合はグラフ内に イルタを検索 4. 出力するメティアタイプに適合するフ ティアタイプを確認 3. フィルタの出力ピンから出力されるメ 合は , グラフ内に読み込む 2. 適合するソースフィルタが存在する場
オプジェク 第 8 回 ポインタの利用を抑える C / C + + でもっとも怖いのがポインタに起因するバグで , 気をつけていてもバ グを発生させてしまうものです。ポインタをまったく使わずにプログラムを作 成することは不可能なので , できる限りポインタを使わずに済ませる手段を披 露します。 もないことが起こります。導出クラスの配 列を基底クラス配列引数に渡すことができ てしまいます。これは " 配列をポインタと みなす " 規則と " 基底クラスへのポインタに 導出クラスのポインタを代入できる " 規則 が適用できるからです。結果として導出ク ラスの配列を基底クラスの配列と勘違い し , 配列の n 番目のアクセスがズレてしま います。 ・初期化を忘れた char * p; * p = / / どこに書いたの ? ! ・範囲外のアクセス char * p = new char [ 5 P [ 10 ] = 'A'; / / おいおい ・解放忘れ void f ( ) char * p = new char [ 5 Palm このところ毎回マクラに登場する PaIm なお話。 CodeWarrior 7 がばくの手元に届 きました。さっそくパッケージひっちゃぶ いてインストールしました。見かけは Rele ase 6 と変わらないですねえ。大きな変更 といえば PalmOS 4.0 対応でしようか。も ちろん従来の PaImOS3.5 にも対応してい return; 〃借りたものは返せ ! ますけど。ともあれこれでばくの m505 で 動くコードが書けるようになりました。新 ・・・まだいくらでもあります。とくに文字 しいオモチャを手にした子供みたいにはし 列の表現に char * を使っていたりすると , ゃいでいます。 palm で C + + プログラミング " 終端記号 ' \ 0 ' を忘れていたためにゴミが残 という , いささか無謀なお遊びに突入しよ る " とか " 確保された領域より長い文字列を うというところです。何がどう無謀かとい コピーした " などもインタのしわざとい ・・いつか必ず書くので , イライラし うと・・ えるでしよう。 C / C + + では配列とポインタ ながら待っててくださいませ。 >PaImOS を同じものとして扱う ( 厳密にはちょっと プログラマ各位様。 違うけど ) ので , 配列に関しても同じこと ポインタは怖い・・・ です。領域を越えたアクセスなんてのは日 常茶飯事 ( では困るんだけど ) ですよね。 C / C + + はプログラマは悪いことをしないと C / C + + において , ポインタは鬼門とまで 信じているので , 配列の範囲外をアクセス いわれています ( たぶん ) 。星の数ほど発生 しても文句ひとついいません。また , 関数 するバグのほとんどが , ポインタの誤った の引数に配列を渡したように見えても , 実 使い方に起因するといわれています ( いい は配列の先頭 ( ポインタ ) が渡されるだけで すぎ ? ) 。ポインタにまつわるバグで思い つくものをざっと並べると , す。さらに C + + では List 1 のようなとんで C + + でのポインタのしわざ #include く土 os セて eam > / / 基底クラス base struct base { int / / 導出クラス derived struct derived : base ( int d ー / / basep[5] の内容をプリントする void print-base(base p[51 ) { fo て ( size—t 土 = の土く + 十土 ) { std::cout くく p[il . b くく ' std: :cout くく std: :endl; int main( ) { derived x [ 5 for ( size—t 土 = i く十十土 ) { / / x[i] . b : 0 , 1 , 2 , 3 , 4 x[i] . d= 9- / / x[i] . d : 9 , 8 , 7 , 6 , 5 print—base(x); 〃 derived[ ] を渡しても合法 て eturn 0 ー / * 実行結果 ... なんてこった . 0 9 1 8 2 82 C MAGAZINE 2001 11
アルゴリズム と同じ要領で , ハッシュ値を元に最初の位 置を割り出し , そこから順番に後ろにたど って目的のデータを見つけます。 以上 ( a ) , ( b ) の方法で衝突が起きても何 とか混乱は避けられるようになりました。 ただ , どちらの方法を選んだ場合もハッシ ュ値の衝突が頻繁に起こるようになるとパ フォーマンスが大きく低下してしまうとい うのも事実です。 ・ ( a ) の問題点・・・・・・衝突が多くなるとリ ストが長くなってしまい , リニアサー チでたどるのに時間がかかる ・ ( b ) の問題点・・・・・・最初に登録されたテ ータ ( 衝突を起こさなかったテータ ) の 回りに新しいテータが集まってしまい , 衝突がさらに頻繁に起こるようになる ( 「一次団子現象」と呼ばれる ) そこで , (a) や (b) を改良した手法とし て次のようなものが考えられています。 ()' ) リストではなく 2 分木を使う 同じハッシュ値を持つデータを単純にリ スト構造でつなげるのではなく , 2 分木に しておきます ( Fig. 6 ) 。すると , 同じハッシ ュ値を持つデータが増えたとしても検索は 比較的高速に行えます。 ( b ' ) 最初に衝突が起きた位置から k2 番目 の位置にテータを入れる 衝突が起きたら単純に「次 , その次 , ・・・」と連続的にたどるのではなく , 「衝突し たら次の位置に入れる。そこも埋まってい たら 4 つ後ろの位置に , それでも埋まって いたら 9 番目 , 16 番目 , 25 番目・・ ・・という ように k2 番目の位置に入れる」ようにしま す (Fig. 7 ) 。こうすると , ハッシュ表の中 でデータが比較的ばらけるので , 団子状態 になることが避けられます。 一般的には , ( a ' ) は必要に応じて用いら れ , ( b ' ) は常に採用されることが多いよう です。 ()' ) の方式で作成したハッシュマッ プのプログラムコードを List 3 に示します。 こまで見てきたような工夫である程度 十分なメモリが大切 List 3 0 else p て土 ntf ( s がマップの中に見付かりませんでした。 % key); break; case 2 : / * 削除 * / p て intf ( ”削除する英単語を入力して下さい・ scanf( ”宅 s % key); wordfound = DeIeteDataFromMap(&hashtabIe, key); if(wordfound ! = NULL) p て in セ f ( s をマップから削除しました。 % key); p て in セ f ( s がマップの中に見付かりませんでした。跏 % key); break; case 3 : / * 全表示 * / PrintA Ⅱ Data ( &hashtab break; } while(n ! = 0 CIeanupHashTabIe(&hashtabIe); return 0 ー / * クリーンアップ * / C + + や Java でのハッシュマップクラス Java には標準のハッシュマップクラスが ーー用意されています。その名もずばり「 Hash Map 」「 HashtabIe 」というクラスで、データ となるクラスの hashCode メソッドを利用 してハッシュ値を生成し , 高速なデータの 格納 & 検索を実現します。 一方 , C + + には標準のハッシューマップク ラスが ( まだ ) ありません。標準クラスライ ブラリの一部である STL には「 map 」クラス というものがありますが物これは通常ツリ ーマップとして実装されることが多く , 残 念ながらハッシュマップではありません。 のデータ衝突の発生には対応できます。デ ータが増えてもあまり衝突が起きず , たと え衝突が起きてもさほどパフォーマンスは 低下しません。ハッシュマップの強烈なパ フォーマンスを維持することができます。 とはいっても , ハッシュ表の大部分が埋ま ってしまうほどデータが増えてしまうと , やはりスピードの大幅な低下は避けられま せん。とくに , ハッシュ表の 9 割程度以上 が使用済みとなった状況下では衝突回数が 爆発的に増え , 実用上の支障が出てきます。 したがって , ハッシュマップは「メモリ を十分に用意することで最高のパフォーマ ンスを得られる方法」だといえます。予想 されるデータ量に対してあらかじめ十分な ただ , 標準 STL を拡張した SGI 製の STL ( ht tp://www.sgi.com/tech/stl/から主な開発環境 用のパッケージをダウンロード可能 ) では 「 hash ー map 」というクラスが含まれており、 これは正真正銘のハッシュマップです。ま た , MFC の CMap クラスもハッシュマップ で実装されています。 なお、上記すべての C + + / Java クラスは , ハッシュテーブルの使用率 ( 「占有率」 ) が一 定の割合以上になるしパフォーマンス低 下を防止するためにハッシュテーブルを自 動的に拡張するように設計されています。 ハッシュマップの劇的なノヾフォーマンスを ハッシュ表の領域を確保しておくことが Ⅲまとめ 利用するための大切な条件なのです。 軽にお寄せください。お待ちしています ! ましたら , 筆者 algo@teamswift.com まで気 この連載についての質問・要望などあり った " 数値計算 " の話題をお届けします。 uble 型や float 型などの浮動小数点演算を使 ッシュマップ」を紹介しました。次回は do でもとくにパフォーマンスに優れている「ハ 操作できる " データ構造「マップ」 , その中 今回は " キーと値の組み合わせを高速に プログラミングの宝箱 1 07
t 1- ⑧ ) 。 initlmage() メソッドでは , 必要に を軽減する効果があります。 て , ガべージコレクションのオーバヘッド 連する資源を明示的に解放することによっ ありませんが , Graphics オプジェクトに関 メソッドを呼び出します。これは必須では 画が終了したら , list 1 のように市 s ( ) phics クラスの各メソッドを利用します。描 とは普通の画面への描画処理と同様に Gra wt. Graphics オプジェクトを取得して , あ 画は , まず getGr 叩 hics ( ) メソッドで java. a します (List 1- ⑩ ) 。バックバッフアへの描 フアの内容をフロントサーフェスにコピー やフレーム数を描画し , 最後にバックバッ て , バックバッフアにスプライト (list 1 ) ) 叩 date() メソッドでは初期化処理に続い ⑥ ) 。 nt クラスのメソッド ) で作成します (List 1- createlmage ( ) メソッド (java. awt. Compone スを使っています。またバックバッフアは 手間が少ないので List 1 では lmagelcon クラ クラスなどを使ってもかまわないのですが ラスを用いる必要はなく , java. awt. TooIkit ージの読み込みには必ずしも lmagelcon ク gelcon クラスを用います ( List1- ⑤ ) 。イメ トパターンの読み込みには javax. swing. lma ックバッフアの作成を行います。スプライ 応じてスプライトパターンの読み込みとバ VOIatiIeImage の宣言と生成 を解説します。 ト List 2 の説明 List2 に関しては , List1 との相違点のみ ッド ) を用います。引数はイメージのサイ メソッド (java. awt. Component クラスのメソ tilelmage の生成には createVOIatileImage ( ) age の宣言 , List 2- ②は生成処理です。 Vola e として作成します。 List 2- ①は VoIatiIeIm List 2 ではバックバッフアを VolatiIeImag ImageCapabiIities ズ ( 横 , 縦のピクセル数 ) です。 前述したように , VolatiIeImage は基本的 Java programmingTips Fig. 3 Vo ti 回 mage を用いたタブレバッファリングの実行画面 0 0 0 ー 5 ま 341 ー FPS 0 0 にビデオメモリ上に確保されますが , 状況 によってはやむをえずメインメモリ上に確 保される場合もあります。プログラムによ っては , いずれのメモリ上に確保されたか によって描画アルゴリズムを使い分けたい 場合などもあるでしよう。 VolatileImage が実際にビデオメモリ上に 確保されたのかどうかを知るには , lmage Capabilities を用います (List 2- ③ ) 。 Volatile lmage クラスの getCapabilities ( ) メソッドで ImageCapabilities オプジェクトを取得し , 続いて ImageCapabilities クラスの isAcceIe rated() メソッドを用いて , VolatiIeImage が ビデオメモリ上にあるのかどうかを検査し ます。 VolatiIeImage がビデオメモリ上に確 保された場合 , isAccelerated() メソッドは VO ね ti 回 mage の検査と復元 e を返します。 モードを表す GraphicsConfiguration オプジ メソッドを用いて , コンポーネントの画面 onent クラスの getGraphicsConfiguration ( ) ドを使います (List 2- ④ ) 。 java. awt. Comp figuration オプジェクトと validate ( ) メソッ かどうかをテストするには , GraphicsCon まずイメージが画面モードに合致するの 内容を復元したりしなければいけません。 必要に応じてイメージを作成し直したり , ドの変化やイメージ内容の消失を検査して , VolatileImage を扱う場合には , 画面モー ェクトを取得し , これを引数にして validat e ( ) メソッドを呼び出します。 validate ( ) メソッ ドの返り値が VolatileImage. IMAGE_INCO MPATIBLE の場合には画面モードが一致 しないので , VolatileImage を作成し直しま す。 次に内容の消失の検査ですが , これには contentsLost() メソッドを用います (List 2- ⑤ ) 。直前に呼び出された validate() メソッ ドの後に VolatiIeImage の内容が失われた場 ロ , contentsLost() メソッドは true を返しま す。この場合は Vola ⅱ lelmage の内容を復元 する必要があります。 List 2 の場合は do - wh ile ループによる繰り返しを行って , バック バッフアの内容を再描画します。 トおわりに 今回は高速描画のテクニックと題して , JDKI. 4 で追加された新機能の 1 つである Vo latilelmage について解説しました。 Volati lelmage を利用したダブルバッファリング では , 従来の lmage を用いたものに比べて 大幅な高速化が図れます。 さらに , 次回解説する予定のフルスクリ ーン排他モードを活用すれば , Java で従来 よりもずっと高速なゲームやアニメーショ ンを実現する可能性がひらけます。どうぞ お楽しみに Java Programming Tips 1 33
C プログラマのための C + + 入門 実践 C + + ゼミナール ので , C + + でもポインタを理解することは やはり C 同様に重要です。 C ではポインタが理解できないというの は , 80X80 , Pentium ?? 系 CPU でインテル 簡易表記の mov eax, (esi) , 680X0 系 CPU で モトローラ表記の move. 1 (a0) , d0 が理解で きないのと同じだとまでいわれるほどハー ドウェアに近い低レベルの概念です。実際 , コンパイラはポインタによるアクセスはこ のような機械語を生成して実行させます。 C + + でも同様の破壊的なまでに強力なポイ ンタは使うことはできますが , C に比べる とキャストによる強力な低レベル操作より はより高級言語らしいスマートな使い方が 主流になります。注意深く設計されたクラ スは C の持つ破滅的なポインタ操作を巧妙 に封じて実用的で安全なポインタ操作を提 供します。このようなクラスはすべてのポ インタ操作の演算子をオーバロードしてい て不正な操作は防いでおり , 型変換につい 代入テストのポインタ版② 0 ー Base int baseVar; で lass De て ive ー : 2 は 0 Base 加セ DeriveVar ー C / C + + における構造体の値渡し 構造化 BASIC などで「型定義」ができ , 参 照渡しを基本とする言語でプログラムを学 この例は典型的な「勘違い」の例です。 C んだほうが陥りやすいワナが「構造体を引 は値渡しなので , func ( i ) 呼び出しでは変 数にする関数」と「構造体へのポインタ」を 数 i をまずスタック上にコビーします。そ 引数とする関数の違いです。「構造体を引 してん nc を呼び出します。血 nc に渡された 数にする関数」は文字どおり構造体そのも のは変数 i のコヒ。ーなので func に実装され のを引数として受け取るのですが , C では ている機能が読み出しだけなら正しく動き この引数はスタック上に通常は確保されま ますが , ただこの過程はたいていの場合は す。構造体は時として非常に巨大なメモリ プログラマが意図した動きではないのです。 の塊であることがあります。構造体を引数 こういった場合はたいてい i 自体が操作対 にする関数はこのメモリの塊を引数として 象物であってスタック上の i のコピーでは 受け取ります。 ないはずです。構造体のサイズが小さい場 struct VAR { 合は大きな問題はありませんが , グローバ ルな変数を状態再現のために構造体として struct VAR 土ー まとめて保持するような場合には構造体自 VOid func (struct VAR x) 体のサイズが非常に巨大になる場合があっ て , その場合にこの例のような処理がある とスタックを破壊して暴走することがあり ます。コンパイラが警告できるような要素 ではないので見落としてしまうこともあり ます。まずは構造体自体を引数にする必要 があるかを検討してみる必要があります。 ても C + + の基本的な型からの変換はすべて 実際は型に別名を付けるだけで , 代入ある サポートされていることがほとんどです。 いはポインタ同士の代入はその型本体同士 それでは C + + では暗黙のポインタの変換は が同じかどうかで決定されます。 List 8 を どのような規則で行われるのでしようか ? 見てください。このような代入は警告にも List6, 7 は代入テストのポインタ版です。 工ラーにもなりません。筆者はこういった クラスへのポインタの変換規則は代入と同 C および C + + の性質には慣れていて違和感 じになっています。派生クラスのポインタ はないのですが , 見かけ上異なった名前の はキャストなしでその基底クラスへのポイ 型を持つ変数同士が何のエラーおよび警告 ンタに代入できます。代入のときと同様に もなく代入できてしまうのは理解に苦しむ 基底クラスが p ⅱ vate に継承されている場合 かもしれません。ですが , typedef は決し はエラーになるのも同じです。 て新しい型を定義するものではないことを C + + ではポインタの代入はキャストなし 忘れなければ大丈夫です。 で以下の場合だけ許されます。 次回 ①同じ型同士のポインタ代入 ②派生クラスへのポインタからその基底 今回は型変換とクラスへのポインタの変 クラスへのポインタの代入 換について勉強しました。 C のポインタを これ以外のポインタの代入はキャストな 理解している人には平易な内容だったと思 しではすべてエラーになります。ここで注 います。次回はクラスメンバのⅵれ ual な関 意しなければならないのは呼 pedef です。 C 数について扱ってみます。最近 , とみに でもそうですが typedef はその読み方から 般的な「バーチャル」という言葉に困惑され 「新しい型を定義」するように聞こえますが , ないように ! void ca 日 e て (void) func ( i 潺 Base を *v0; Derive *vl ー void test (void) v0 = vl; vl = v0i 型本体同士が同じなら代入できる typedef 土 n し INT; typedef int 工 nteger; v 引の IN で lnteger *ptr—va 10 INT lnteger * p にて一 v 1 void test( void ) va10 言 v 引 vall = v 引 ptr—va10 ptr—vall; ptr—vall ptr—va10; / 2 C MAGAZINE 2001 11
スタートアップ Ja a Java 言語事始 このボタンを押してみると・・・・・・クリック時 イベントリスナと呼んでいます。 ActionEvent e = new ActionEvent(); の反応のほかは何も起きませんね。ボタンが まず ActionListener を実装したクラスの 〃ⅱ stene 「は以前に与えられたリスナ 記述。たとえばコンソールに文字列を出力 押されたときの処理を書いてないので , これ //listener の actionPerformed を呼び出す はあたりまえです。問題はどうやって「ボタ if(listener ! = null)listener させるためには , List 6 の ActionHandler の ンが押されたときの処理」を書くかです。 ようなクラスを宣言し , actionPerformed actionPerformed(e); メソッドの中にさせたい処理を記述します。 ーリスナの登録 ちなみに , 引数の ActionEvent は , イベン というような処理を行っています。もっと ボタンが押されたなど , コンポーネント トに関する情報がいくつか入っているオプ も , 実際にはもう少し複雑ですが ( 上記に に対する操作のことをイベントといいま ジェクトです。必要がないなら使わなくて はリスナが 1 個だけだが , 実際には何個で す。イベントが発生したとき何かの処理を も追加が可能 ) 。 かまいません。今は使いません。 させるためにはコールバックの方法を使い このオプジェクトを作成し , addActionL Iist6 を実行して , コンソールが見える状 ます。後で起動するためのメソッドを , イ 態でボタンを押してみてください。ボタン istener メソッドでボタンに与えると , のち ンタフェイスを使ってボタンに渡すわけで にイベントが発生したとき , 与えたリスナ を押すたびに文字列が表示されるはずです。 す。この場合「後で起動するためのメソッ の中の actionPerformed メソッドが起動さ なお , リスナは ActionListener のほかに ド」は actionperformed() といい , それは も存在します (TabIe 1 ) 。概念的にはみな れるわけです。 ActionListener インタフェイスで宣言され 概念的にはボタンの中で , 共通するので , API ドキュメントを参照し イベントが発生した ( ) { ながらこれまでの説明を応用して使用して ています。ちなみに , これらコールバック //ActionEvent オブジェクトの生成 , として使うインタフェイスをリスナまたは ください。 List TabIe 1 主な Listene 「と使用するコンポーネント ( 一部のみ ) 6 押すとコンソールに表示するボタン コンポーネント Listener ボタン , テキストフィールド , コンボボックス ActionListener スクロールバー AjustmentListene 「 ボタン , スライダ ChangeListene 「 Component—般 ComponentListene 「 Component—般 FocusListener ボタン , コンボボックス ltemListener Component—般 KeyListener メニュー MenuListener Component—般 MouseListene 「 フレーム , ダイアログ WindowListener リスナに無名内部クラスを使う 7 / / S p 厄 7. 扣 v より抜粋 public static void main(string[ ] args)( / / ボタンオブジェクトの作成 JButton b new JButton( ″ 0 し I ( K に / / 無名内部クラスの宣言と生成 ActionListener 引 new ActionListener( ) { public VOid actionPerformed(ActionEvent e)( system ・ 0uしPr土n凵れ( ″ニ度とこのボタンを押さないでください第 / / s が e6. iava より抜粋 ツん冖 ー / / イベント処理のためのインタフェイスとクラス import java. awt. event.ActionListener; import java. awt. event. ActionEvent;« 0 lass Samp 厄 6 ( public static void main(string[ ] args) ( 〃ボタンオブジェクトの作成 JBu しし 0 れ b new JButton( "CLICKI"); 〃リスナオブジェクトの作成 ActionHandler 曲 = new ActionHandIer( ) 〃ボタンにリスナを登録 わ . addActionListener(ah); 〃フレムのコンテンツベインの取得 container c 莽 . getContentPane( 〃コンテンツベインにボタンを乗せる c. add(b); 〃リスナ ( Ac 0 仙辷 tene てを実装したクラス ) class ActionHandIer implements ActionListener{ public VOid actionperformed(ActionEvent e){ system. out.println(" ニ度とこのボタンを押さないでください” List 〃ボタンにリスナを登録 b. addActionListener()l スタートアップ Java
List 11 アクションコマンドを使う / / s e11. java より抜粋 class Sample11{ public static void main(Stringt ] args) { / / カウンタ機能を持ったパネルオブジェクトの作成 PIusMinusCounterPanel cp = new PIusMinusCounterPaneI ( / / フレームのコンテンツベインの取得 container 0 共 . getcontentpane(b; / / コンテンツベインにパネルを乗せる ℃ . add(cp); List 増減のできるカウンタ //sample10. java より抜粋 class Sample10{ public static void main(String[J args){ 〃カウンタ機能を持ったバネルオブジェクトの作成 ?IusMinusCounterPaneI cp = new PIusMinusCounterpanel( 〃フレームのコンテンツベインの取得 container C jf. getcontentpane( 〃コンテンツベインにパネルを乗せる 0. add(cp)i 当 class PlusMinusCounterPanel extends JPanel implements ActionListener{; 〃数値を保持する int 00u セ = の 〃ラベルオブジェクトの作成 JLabel ね b 引 new JLabeI に 0 / / ボタンオブジェクトの作成 JButton plusButton = new ÜButtoncm 十 JButton minusButton キ new JButton(" / / リスナとしての動作 public void actionPerformed(ActionEvent e)( Object source = e. getsource( リ if(sou て ce = pIusButton)( / / 数値をインクリメント ー count 十十 = 引 jelse if(source = = minusButton)( / / 数値をデクリメント count— c lass 円 usMånusCounterpanel extends JPaneI imp lements ActionListener ( / / リスナとしての動作 public V0id actionperformed(ActionEvent e) { string command ま e. getActionCommand( if(command. equals("plus")){ 〃数値をインクリメント count 十十一 )else if(command. equals("minus") ) ( 〃数値をデクリメント count——ー labeI.setText()n ~ 十 count); / / コンストラクタ PlusMinusCounterPanel( ) { 誉 / / ボタンオブジェクトにアクションコマンドを設定 pIusButton.setActionCommand(npIus"); minusButton. setActionCommand ( "minus ″ label.setText(" 十 count); / / コンストラクタ PlusMinusCounterPanel( ) ( ルを継承して機能をまとめる」方法を紹介 く , フレームやアプレットを使って同様な 構造を作るときもあります。使い方を覚え しましよう。 List 9 を見てください。クラスが 2 つあり ておいてください。 ます。 CounterPanel クラスは JPanel を継承 リスナの共用 していて , さらに ActionListener を実装し ています。また , フィールドとしてラベル とボタンを持っています。 実際のプログラムには多数のボタン ( な つまり , このクラスは「パネル」「リス どのコンポーネント ) が存在します。動作 ナ」「コンポーネントの保持」の 3 つの役目 がまったく違う場合はそれぞれにリスナを ーバネルに機能をまとめる 記述すればいいのですが , 「ちょっと違う を持っているわけです。 だけ」という場合があります。たとえば , actionPerformed メソッドが中にあるの 前述の制限があるので , ローカルクラス でフィールド (count やボタンやラベル ) を 候補色のボタンからフォントの色を選ぶ場 を使うと複雑な動作をさせるのが難しくな 合など , パラメータのみ違うのだからリス ることがあります。ローカルクラスの使用 扱うのに便利 , new すれば必要なコンポー をやめ , リスナをきちんと設計すれば問題 ネントが一度に生成されるので使いやす ナを共有したくなるけれど , するとイベン はなくなりますが , 別の方策として「パネ トがどのボタンから送られたものか調べる い , などの利点があります。パネルではな Fig. 11 List 1 0 の実行画面 諏 .. ロ回函 ロ 80 C MAGAZINE 2001 11
開発ツレ 0 も。と便利朝使おう / Borland DeIphi や BorIand C + + Builder には , コンボーネントやプロバ ティのクリックでウイザードが起動するなどの IDE の機能拡張をするため の「 Open TooIs AP 凵が装備されています。本章では Open Tools API を用いた機能拡張方法を具体例をいくつかあげて解説します。 BorIand DeIphi を Open TOOIS A 円で機能拡張する 佐々木隼人 0pen T00 A 円による機能拡張 Borland Delphi ( 以下 Delphi) は機能拡張 のための OpenToolsAPI というインタフェ イス群を備えています。 OpenTooIsAPI で は , ・フォームデータやソースコードを生成 支援するためのウィサードの作成 ・メニューに新しいコマンドを追加 ・ IDE の何らかの動作 ( ファイルを開くな どの ) を通知として受け取って動作す る , アドインなどを作成するために必 要なインタフェイス ・ De hi の動作のカスタマイズ ・コンポーネントエテイタやプロバティ 工テイタのための基底クラス といったことが提供されています。 Open T001S API のユニットは , Delphi の ProfessionaI 版または Enterprise 版などの上 位版で提供されています。 Learning 版では 利用できません。ただしコンパイル済みの パッケージまたは DLL なら往 arn ⅲ g 版でも 利用できます。 Open Tools API は , Delphi のバージョン が上がるごとに変更が加えられています。 Delphi 4 では inte ace 構文が使われている ために , 旧バージョンと互換性がないもの もあります。またⅲ te ce 構文形式に置き 換わったインタフェイスは DeIphi4 以降ほ とんど変更が加えられていません ( コンパ イラや BorIand C + + BuiIder のために多少変 更があるが機能的な変更はない ) 。今後は i nterface 構文形式の Open T001S API で新機 能が提供されていくと思われるので , interf ace 構文形式で開発することをお勧めしま す [ 注 2 ] 。これらの inte 血 ce 構文形式のインタ フェイスは ToolsAPI というユニットにまと められています。 本章では , interface 構文形式での Open T ooIsAPI の利用方法を取り上げます。バー ジョンは Delphi5 または 6 で動作することを 想定しています [ 注 2 ] Delphi 6 では旧形式の Open T00 API のソースコードが提供されなくなっている ウイザド を使用すると実装の手間を省くことができ t クラスが定義されているので , このクラス 行わないメソッドで実装した TNotifierObjec トには IOTANotifier インタフェイスを何も スには定義されています。 ToolsAPI ユニッ ないメソッドが IOTANotifier インタフェイ ているのですが , ウイザードでは使用され は IOTANotifier インタフェイスから派生し 要があります。 IOTAWizard インタフェイス インタフェイスを実装したクラスを作る必 ウイザードを作成するには , IOTAWizard ウイザードを構成するクラス 機能をウイザードと呼びます。 ンソールアプリケーションなど , 作業支援 れるリポジトリの [ 新規作成 ] タブにあるコ [ ファイル (F) ] → [ 新規作成 (N) ] で表示さ ザード ( F ) ] で実行される作成支援機能や , ューの [ データベース (D) ] → [ フォームウィ ートと呼ばれていました。 Delphi のメニ 「ウイザード」です。 Delphi3 以前はエキス Open T001S API の使用で代表的なものが ます。独自のメソッドで実装したい場合は TInterfacedObject クラスを使用して自分で 各メソッドを実装するか , 実装を行いたい メソッドだけオーバライドするとよいでし よう。 IOTAW1zard インタフェイスだけが実装 されたウイザードは , 一般的に「アドイン ウイザード」と呼ばれています。 IOTAWizar d インタフェイスにはいろいろなメソッド が定義されていますが , アドインウイザー ドの場合は GetIDString メソッドと GetNam e メソッドのみ使用されます。 GetIDString メソッドはユニークな識別子 を返すようにします。 GetName メソッドは ウイザードの名前を返します。そのほかの メソッドは後述するウイザードで使われま す。アドインウイザードではウイザードの 呼び出し方法は定義されていません。呼び 出す必要がなく何かを監視するウイザード でもかまいません。自由に作ることができ るのがアドインウイザードです IOTAFormW1zard インタフェイスまたは IOTAProjectWizard インタフェイスを実装 すると , リポジトリから実行されるウイザ ードを作成することができます。それぞれ 「フォームウイザード」 , 「プロジェクトウィ ザード」と呼ばれています。リポジトリと は DeIphi のメインメニューの [ ファイル (F) ] → [ 新規作成 (N)] で呼び出される [ 新規作 成ダイアログボックス ] に表示される内容 です。メソッドとしては , ・ GetAuthor メソッド ウイザードの作成者を返す ・ GetComment メソッド ウイザードの簡単な説明を返す 特集 1 開発ツールをもっと便利に使おう !