value - みる会図書館


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

1. 月刊 C MAGAZINE 2001年12月号

オフジェク 第 9 回 オプジェクトのコピー問題を解決する 生成したオブジェクトのインスタンスをコピーするような場合 , をクラスの実装 方法によっては d 引 ete 時に問題が起きたりにメソッドに参照できなくなるな →、どの不具合が生じます。今回はこうしたオブジェクトのコピーに関する問題を 解決します。 かに℃ ounter' (List 1 ) があります。・・・・・・何 回り始めた 1986 年ごろに書いたもののよ のヒネリも技巧もない , よくいえば、素直 ' , うです・・・・・・思い出しました。当時の C + + コ ンパイラは c 仕 ont と呼ばれる C + + から C への 悪くいえば。くだらない ' クラスです。ハー ドディスク上に作られた昔々に書いたコー トランスレータで実現されていました。 cf 先日ふと思い立って , 熱き血潮が僕の体 ド置き場 ( 墓場 ? ) にも , やはり Counter が ront は C + + コードをいったん C に変換し , を流れていたころに擦り切れるまで聴いて 冷凍保存されていました。解凍して眺めて その出力を C コンパイラに与えていました。 いたレコード ( ホントに擦り切れちゃった したがって現在主流のネイテイプな ( 直接 んで CD で買い直したけど ) を引きずり出し みると , なんとも不思議なコードが姿を現 しました (List 2 ~ 5 ) 。カウンタのふるまい オプジェクトコードを出力する ) コンパイ て , 夜が明けるまで聴いていました。 ラと比べて C + + から C への変換作業が必要 いいなあ , 四半世紀も前の曲なのにまった を実装したクラス CounterImp を作り , Cou になるので , それだけ。遅い ' のです。一般 く旧さを感じさせない。若かりしころの熱 nter は CounterImp のポインタを保持してい に C + + のヘッダは C より大きくなりがちで き血潮が少しだけよみがえる思いです。何 ます。そしてカウンタのメソッドのほとん すし , ヘッダはコンパイルのたびに何度も 聴いてるかって ? 「危機 (Close to the Ed どはそのまま CounterImp に横流し ' され (e)/Yes 」です。歳がバレちゃうね ^ ^ ; ています。、お試しとはいえ , なぜにわざ 読み込まれるのですから , 遅いコンパイラ わざこんなややこしいコードを書いたのだ にとって大きなヘッダは非常につらいので 思い出の詰まった古いレコードや本はそ す。そんなわけで " Counter. h " からカウン ろうと , しばし回想・・ ・・コメントに残され う簡単には捨てられないように , いつまで た日付によれば , C + + 処理系がようやく出 タのメンノヾ変数とふるまいを CounterImp に もディスクの隅っこにひっそりと , 昔書い たコードが眠っています。いつもはほとん Counter' プログラム 過去に作成した℃ ounter' (Counte 「 . h) どアクセスされることのない , まさしく 。ディスクの肥やしでしかないのだけれど , ときとしてこれが問題解決の大きなヒント をもたらしてくれることがあり , なかなか 捨てられません。今回はそんなゴミの山か ら見つけたお宝のお話です。 15 年前のカウンタ はじめに / / Counter. h #ifndef —_COUNTER_H— #define ——COUNTER—H—— C I ass Counter ( class CounterImp* imp—; public: Counter ( Counter ( void incr( ); void decr( 土 n に value( ) const; #include <iostream> c ー ass Counter { int va ー ue—ー public: counter() : vaIue-(0) { } -counter( ) { } void incr( ) { 十十 value-; } void decr( ) { --value-; } 土 n し va ー ue ( ) const { retu て n va ー ue— 筆者が新しいコンパイラを手に入れて最 初に書くのはおなじみ。 He Ⅱ 0 , world " のほ int main( ) { Counter c ー c. incr(); std::cout くく c. value(); return 0 ー #endif / 2 C MAGAZINE 2001 12

2. 月刊 C MAGAZINE 2001年12月号

c. incr(); / / ー 1 のように , ふるまいをダイナミックにすり替 えることができるわけです ( List17 ~ 24 ) 。 デザインパターンの用語では以トラテジ (Strategy) パターン ' と呼ばれています。 ついでに同じ機能を Java で実装してみま 作成した Counte 「 Facto Ⅳクラスを 利用する (ReverseCounterImp. h) / / ReverseCounterImp. h #ifndef L—REVER 、 SECOUNTER. IMP-H—— #define —REVERSECOUNTERIMP—H— #include ncounterlmp. hÉ class ReverseCounterImp : public CounterImp { おまけ : Java による実装 20 #endif virtual void decr ( virtual void incr( ) ・ public: 利用する (ReverseCounterlmp. cpp) 作成した Counte 「 Facto Ⅳクラスを 21 #include "ReverseCounterImp. h" / / ReverseCounterImp. cpp した (List25)0 C + + と比べると極端なまで に簡単です。 count-ptr に相当するものも なければ handle-base/body-base もありま せん。 Java がウケるのもうなずけます。 co unt ー ptr は安全な領域解放のためのからく りです。 Java は参照カウントよりかしこい アルゴリズム (mark&sweep 法じゃなかっ たかな ) によるガべージコレクション ( 使用 済み領域の解放 ) が Java 、 ( 仮想機械 ) 内部 CounterFactory クラス本体 (CounterFactory. cpp) 23 / / CounterFactory. cpp #include "CounterFactory. h" #inc lude ″ NO て m 引 counter lmp. れ” #include "ReverseCounterImp. h" counter Counterpactory: :makeNormal( ) ( return new NormalCounterImp; Counter CounterFactory: :makeReverse( ) { return new ReverseCounterImp; CounterFactory クラスを 試用するコード (trial.cpp) 24 / / セ r 土引 . cpp #include く iostream> #include "counte て Factory. h" VOid print—va ー ue ( counter counter ) ( std: :cout くく ncurrent value: < く counter. value( ) くく std: :endl; Counter c ー int 土 n ( ) ( return 0 ー くく c. value() くく std::endl; std: :cout くく nafter decr. c. decr(); print-value(c); くく c. value( ) くく std: :endl; std : : cout くく nafter incr. c. incr(); くく c. value( ) くく std: :endl; Std : : cout くく”土 ni し土 a ー c = CounterFacto て y : : makeReverse ( くく c. value( ) くく std: :endl; std: :cout くく nafter decr. c. decr(); print—value(c); くく c. value() くく std::endl; std: :cout くく nafter 土 nc て . c. incr(); くく c. value() くく std::endl; std: :cout くく "initial C = CounterFactory: :makeNormal( オプジェクト工房 に仕込まれているので , プログラマがわざ わざ書く必要はありません。 handle-body も いらないんです。 Java におけるクラス・イ ンスタンスそれ自体がオプジェクト ( body ) の handle なのです。 Java には Handle-Body のイディオムも組み込まれているといって もいいでしよう。 ・・まったく , 前々回の CGI といい , なぜ に C + + にこだわり続けるんでしようね ^ ^ ; 。 Java 版 Counter 25 ノ / CounterDemo. java interface Counter { void incr( void decr ( 土 n し value( abstract class CounterImp 土 m が e に n セ s CO Ⅲ北 e て { protected 土 n し value— = 0 ー public abstract VOid incr( public abstract void decr( public 土 n セ value( ) { return va ー ue— c ー ass Norma ー CounterImp extends CounterImp { public void incr( ) { 十十 value— public void decr( ) { class ReverseCounterImp extends counterlmp { VOid ReverseCounterImp: —value— VOid ReverseCounterImp 十十 va ー ue—ー :incr() { CounterFactory クラス本体 (CounterFactory. h) 22 / / CounterFactory. h #ifndef -.. -COUNTERFACTORY-. H— #define —COUNTERFACTORY—H— #include ” coun セ e て . h ” c ー ass CounterFactory ( public: static Counter makeNormal( 潺 static counter makeReverse ( #endif public void incr( ) { —value—; public void decr( ) { 十十 value— C ー ass CounterFactory { static public counter createNormaI( ) { return new Norma ー CounterImp ( static pub ー ic Counter createReverse ( ) { return new ReverseCounterImp( pub lic c lass CounterDemo { static public void main(string[ ] arg) ( Counter c ー system. out. println( ” NO て ma げ 0 = CounterFactory. createNorma Ⅱ c. incr(); system. out.println( ″″十 c. value( ) c. decr( system. 0uしPて土n凵n( 十 c. value( ) system. out.println( "Reverse"); c = CounterFactory. createReverse ( c. incr(); System. out. println()" 十 c. value()); c. decr(); System. out. println( 十 c. value( ) どびてククのオブジェクト工房 77

3. 月刊 C MAGAZINE 2001年12月号

追い出すことで , 利用者側が include しな ければならないへッダのポリュームを抑え ています (CounterImp のヘッダは利用者に は公開しません ) 。このからくり , ライプラ リとその利用者が同時に開発を進めるとき に有効です。ライプラリをどれだけ書き換 えようが , 外部インタフェイスに変更がな ければヘッダの変更はなく , 利用者側はリ ンクし直すだけで済むからです。 コピーが引き起こす問題 さて , この 15 年前に書かれたカウンタ , 1 つ大きな穴が開いているのにお気づきで しようか。利用者とのインタフェイスであ る Counter はそのコンストラクタで Counte 過去に作成した℃ ounte 「 ' て eturn imp—->value( 土 n セ Counter: :value( ) const imp—->decr ( ) 引 void counter: :decr( ) { imp——>incr ( void counter: :incr( ) { de ー ete imp—ー Counter: :¯Counter( ) { new CounterImp; imp— counter : : Counter ( ) { #include "counterlmp. hN #include "Counter. h" / / counter. cpp (Counte 「 . cpp) #endif int value( ) const; void decr ( void incr ( CounterImp( counterlmp( public: int va ー ue— class CounterImp { #define —COUNTERIMP—H— #ifndef —COUNTERIMP—H— / / counter lmp. h (Counterlmp. h) 過去に作成した・ Counte 「 ' rlmp を new, デストラクタで delete していま す。何の欠陥もなさそうですが , ここ力、ラて なんです。こんな関数を書いたとしましょ VOid print—counter (Counter (l) { std: :cout くく cl. value(); コピーが行われます。これにより c0 と cl は 渡されるとき , c0 から cl ヘインスタンスの main から print—counter へカウンタが引き return 0 ー c0. incr(); print—counter ( cO Counter c0 ー 土北 main ( ) { (Counterlmp. cpp) 過去に作成した℃ ounte 「 ' / / counterlmp. cpp #include "counterlmp. れ″ counterlmp: :counterlmp( ) CounterImp: : CounterImp( ) :value() :incr() { : va ー ue VOid CounterImp : 十十 value— void CounterImp 土 n し Counter lmp : return va ー ue— const オプジェクト工房 1 つの CounterImp を共有します。 print_cou nter から脱出するとき , cl がデストラクト , つまり CounterImp が delete されます。した がって print—counter を抜けた後 , cO はもは や使いものにならなくなっています。この コードを実行するとおそらく c0. incr ( ) した とたんに、吹っ飛ぶ ' でしようね。 コピーをさせないようにする この問題を避ける手はいくつか考えられ ます。 1 つはピーできなくしてしまう ' というもので , これは簡単です。 c ー ass Counter { private : Counter (const counter&) , Counter& 0 ra セ or = ( const Counter&); 原本 / 複製なのかを示すフラグを 持たせる (Counte 「 . cpp) / / counter. cpp #include "counter. h" #include "counterlmp. h" counter : : counter ( ) { imp—ま new CounterImp; copied— = fal se ー counter : : counter ( const counter& that ) { imp— = that.imp—; copied— = true ー Counter: :¯Counter( ) { if ( !copied— ) delete imp—; counter& counter : : operator= ( const counter& 原本 / 複製なのかを示すフラグを 持たせる (Counter. h) / / Counter. h #ifndef ——COUNTER—H— #define —COUNTER-H— C ー ass counter { c ー ass counterlmp* imp—ー bO 引 copied—; public: co 聞セ e て ( Counter ( const counter& counter ( Counter& operator= ( const Counter& void incr( 潺 void decr( 潺 int value( ) const; #endif that) ( if ( this = = &that ) return * this ー if ( !copied— ) { delete imp—; imp- = that. imp-; copied— = true; return *this ー void Counter : : incr ( ) { imp——>incr ( void counter: :decr( ) { imp—->decr ( 土 n セ counter: :value( ) const { て e 加て n imp-->value( れびてククのオブジェクト工房 / 3

4. 月刊 C MAGAZINE 2001年12月号

オフジェクト工房 -ptr と機能的には同じですけど ) 。クラス てこれを class CounterImp; に取り替える テンフレート count_ptr<T> はあたかも T* と , 今度はコンパイルエラーとなってしま であるかのようにふるまい , デストラクト います。 count-ptr は template 引数 ( ここで 時に参照数を確認してそれが 0 なら delete は CounterImp) の宣言がなくてはコンパイ します。 ルできないのです。 CounterImp * を count_ptr<CounterImp> オプジェクト指向がこの問題を解決しま に置き換えた Counter が List 9 , 10 です。 す。 Counter と CounterImp の間には , 'n 個 の Counter が 1 個の CounterImp を握ってい 実装詳細を include しない工夫 る " という関係があります。そこで , これと これで万事解決 ? ・・・・・・残念でした。これ 同じ関係である handle_base と body_base では " 実装詳細を include しない " という本 を用意します。 来の目的をご破算にしてしまうのです。コ c I ass body-base { ・・ ードを見ると #include "CounterImp. h ' と明 c lass handl e—base { 己されているじゃありませんか。かといっ count—ptr く body—base> bOdy—ー 完成した Counte 「 (body_base. h) 完成した Counte 「 (CounterImp. h) そして handle_base から Counter を , body_ base から CounterImp を導出します。 class CounterImp : public { public { c lass Counter これで Counter の宣言には CounterImp の 宣言が不要となりました。ただし , hand le-base が持っているのは body-base * です から , Counter が CounterImp を利用する際 完成した Counte 「 (Counte 「 . h) 一三ロ 13 15 / / body—base. h #ifndef -—BODY—BASE—H— #define —BODY—BASE—H—— class body—base { public: virtual body-base ( ) { } protected : body-base( ) { } private: body—base ( const body—base& body—base& operator= ( const body—base& #endif / / counter.h #ifndef —COUNTER—H— #define —COUNTER—H—— #include "handle—base. h" class CounterImp; class Counter : public handle—base ( Counter lmp& bOdy ( Counter ( void incr( void decr( int value( ) const; / / CounterImp. h #ifndef —COUNTERIMP-H— #define ——COUNTERIMP—H #include body—base. h" class CounterImp : public body—base { 土 n し value— public: counterlmp( counterlmp( void incr ( ); void decr ( 潺 土 n し value( ) const; public: #endif #endif 完成した Counte 「 (CounterImp. cpp) / / counterlmp ・ cpp #include "counterlmp. h" counterlmp: :counterlmp( ) : value counterlmp: :¯counterlmp( ) :incr() { void Counter lmp : 十十 va lue— :decr() { void CounterImp : —value ・ :value() 土 n セ counterlmp. const return va ー ue— 完成した Counte 「 (handle—base. h) / / handle—base. h #ifndef —HANDLE—BASE—H— #def ine —HANDLE-BASE-H— #include ncount—ptr. h" #include "body—base. h" class handle—base { public: ーは d ( ) con 旧し { て e 叮ー y. 一 t ( ) ! = } protected : handle-base( ) { } handle>se(body-base* bdy) : body-(bdy) { } handl e—base ( const handl e—base& that ) body 」 that. body-) { } handl e—base& operator= ( const handle—base& that) { if ( &that ! = this ) body-= that. body- return *this ー b)dy( ) const { return *body-; } protected : count—ptr<body—base> bOdy #endif 完成した Counte 「 (Counte 「 . cpp) / / counter. cpp #include "counter. h" #include "counterlmp. h" coun セ : : Ⅲ北、 ( ) : ⅵ厄ー ( n cotmterlrp) { 12 Counter : : ¯counter ( ) { counter lmp& counter : : b0dy ( ) { return static-cast<CounterImp&>(handle—knse: ) VOid counter: :incr( ) { body() . incr( void counter: :decr( ) { body( ) . decr( int counter: :value( ) const ( て e セセー cas セぐ 0 ロ北 er わ ( セ s ト > / ). 一 ( どびてククのオブジェクト工房

5. 月刊 C MAGAZINE 2001年12月号

には body_base * から CounterImp * へ cast- down しなければなりません。 handle_base に参照する body_base を返す protected メソ ッドを用意し , 導出クラス Counter で body base から CounterImp に cast-down します (L Handle-Body istll ~ 16 ) 。 Fig. 1 Han 引 e - Body を用いたクラス階層 ます。 HandIe の値渡しはすなわち Body の 付いた本体 ( Body ) がいっしょに付いてき アッチからコッチに移動するとそれに貼り ディオム ( 定石 ) です。取っ手 (HandIe) を HandIe-Body と呼ばれる , かなり有名なイ ェクトを陬つ手 ' ど本体 ' に分離する手法は いかがでしよう。解説したようにオプジ 参照渡しとなります。 Handle 自体はとて もコンパクトなので , コピーにかかるコス トはとても低く抑えられます。 Handle-Body のちょっとした応用を紹介 しましよう。 Counter はメソッド incr() で + 1 , decr ( ) で -1 しますが , 符号を逆にした カウンタを考えましよう。 incr ( ) で -1 , de cr ( ) で + 1 する逆転カウンタです。通常カウ ンタと逆転カウンタとを 1 つの変数でダイ ナミックに切り替えることができるんです。 カウンタのふるまいを決定するのは Body 側です。そこで Fig. 1 のようなクラス階層 を組みました。 CounterImp のメソッド incr ( ) / decr ( ) は純粋仮想関数とし , 導出クラ ス NormalCounterImp ( 通常カウンタ ) /Rever seCounterImp ( 逆転カウンタ ) のそれぞれで 再定義します。 counter の incr()/decr() は Body すなわち CounterImp の incr ( ) /decr ( ) を単に呼ぶだけですから , Counter の Body が NormaICounterImp と ReversecounterImp のどちらであるかによってふるまいが変化 します。 カウンタ工場 : CounterFactory というク ラスを用意しました 。こいつの static メソ ッド createNormal ( ) は NormalCounterImp createReverse ( ) は ReverseCounterImp を Body とする Counter を生成します。これを 使って , Counter C ー c = CounterFactory: : createNorrna c. incr(), ・ / / 十 1 c = CounterFactory: :createReverse ( 潺 handle base Counte 「 body—ba se Counterlmp Oincr0 Odec 「 0 •value0 NO 「 ma ℃ ounterlmp •inc 「 0 Odec 「 0 ReverseCounterlmp Oinc 「 0 Odec 「 0 作成した Counte 「 Facto Ⅳクラスを利用する (Counterlmp. h) 〃 CounterImp. h #ifndef —-COUNTER 、 IMP-H—— #define ——COUNTER. IMP—H—— #include "body-base. h" class CounterImp : public body—base { protected : int value—; public: CounterImp( ¯counterlmp( ) デ virtual void incr ( ) = virtual void decr ( ) = の 土 n し value( ) const; #endif 作成した Counte 「 Facto Ⅳクラスを利用する ()o 「 ma ℃ ounterlmp. cpp) / / NormaICounterImp ・ cpp #inc lude "Normal CounterImp. h" void NormaICounterImp: :incr( ) { 十十 value—; void NormaICounterImp: :decr( ) { —value— 76 作成した Counte 「 Fact0 Ⅳクラスを利用する (Norma ℃ ounterlmp. h) / / NormalCounterImp. h #ifndef ——NORMALCOUNTERIMP—H—— #define ——NORMALCOUNTERIMP-H- #include "CounterImp. hm class NormalCounterImp : public counterlmp { public: virtual void incr( ); virtual void decr( 18 #endif C MAGAZINE 2001 12

6. 月刊 C MAGAZINE 2001年12月号

のように , コピーコンストラクタとコピー 演算子を private 部に宣言。だけ ' しておきま す。そうすればうつかりコピーしようとす るとコンパイルエラーとなりますし , たと えコンパイルが通っても実装されていない のでリンク時に引っかかります。だから関 数の引数として受け渡すには参照もしくは ポインタ渡しに限定されます。 原本 / 複製なのかを示すフラグを持たせる コヒ。ーできない / させないというのはあ まりに無愛想ですかね。 Counter を戻り値 とする関数のたぐいを書くのがとてもめん どうになりそうですしね。安全なコピーを れます。 cl が原本 delete cl; 一 60 = * cl ー Counter * cl Counter c0 ー のです。 実現し , 値渡しを可能にすることを考えま す。安全にコピーするには・・・・・・問題はデス トラクタで CounterImp を無条件に delete し ていることなのですよね。ならばオプジェ クト内部に原本なのか複製なのかを示すフ ラグを持たせ , デストラクタでは自分自身 が原本であるときに限り delete するという のはいかがでしようか ( List6 , 7 ) 。 残念でした , これでもダメな場合がある new Count er ー このコードのグレー部分でコピーが行わ c0 が複製です。次の delete cl によって原本がなくなりますか ら , その後の複製 (c0) は無効な CounterIm p を保持しています。正しく動作するはず がありません。 参照カウントを利用する ではどうするか , その解の 1 つが前回 boost::shared_ptr で少しだけ紹介した。参 照カウント ' です。「部屋に入ったら正面の 黒板に書いてある数を + 1 しろ」 , 「退室す るときにはその数を -1 しろ」 , 「その結果が 0 となったら部屋の明かりを消しておけ」と いうわけです。最後まで使っていた人に後 始末してもらうからくりです。 List 8 に参 照カウントを仕込んだポインタもどå'co unt-ptr を示します ( つまりは boost::shared 参照カウントを使った count ー pt 「 . h / / count—ptr. h #ifndef ——COUNT—PTR—H— #define ——COUNT—PTR—H—— temp ー ate <C ー ass X> class count—ptr ( ptr—ー uns igned* cnt— VOid inc—ref ( const count—ptr& て void dec—ref ( public: explicit count—ptr()* p = 0 ) : p セて」 p ) , cnt—(O) { if (this ! = (r) { dec-ref(); inc—ref(r); count—ptr& operator= ( const count—ptr& て ) { dec-ref ( ) count—ptr ( ) count—ptr(const count—ptr& て ) { inc—ref(r) { if ( p ) cnt- = new unsigned(l); } C MAGAZINE 2001 12 #endif 十十 *cnt— if ( cnt- ) て . cnt—; cnt— ptr— て . ptr—ー template <class X> p セて一 cnt— 0 ー del ete pt て de ー ete cnt—ー if (--*cnt- if ( cnt- ) { in line VOid count—ptr く X> dec—ref ( ) { temp ー ate <C ー ass X> unsigned count ( ) const { return cnt— ? *cnt— bO unique ( ) const X* get( ) const X* operator-> ( ) const X& operator* ( ) const } return *this, { return * p して一デ } { return ptr—ー } { return ptr—; } { return cnt— ? *cnt— = = 1 . true in line VOid count—ptr く X> : : inc—ref ( const count—ptr<X>& て ) count_pt 「 <CounterImp> に置き換えた Counter. h / / Counter. h #ifndef ——COUNTER—H— #define ——COUNTER—H— #include ncount—ptr. h" #include "counterlmp. h" class Counter { count—ptr<CounterImp imp—; public: counter ( Counter ( void incr( void decr ( 土 n し value( ) const; #endif count_pt 「く CounterImp> に置き換えた Counter. cpp / / counter. cpp #include "Counter. h" Counter: :counter( ) : imp—(new CounterImpy { Counter: :¯counter( ) { void counter : : incr ( ) { imp—->incr( void counter: :decr( ) { imp-->decr( 潺 土 n ヒ counter: :value( ) const { return imp—->value(

7. 月刊 C MAGAZINE 2001年12月号

C プログラマのための C + + 入門 実践 C + + ゼミナール 仮想関数呼び出しのカラクリ . zero 12 List 6 のコンバイル結果 MoveCa 凵 er——FP5Moves : pushl %ebp %esp,%ebp $8 ,%esp subl 8 (*ebp) ,%edx movl $-8 ,%esp addl 8 (%edx) ,%ecx mov ー $0 push ー 8 (%ecx ) , %eax movsw ー addl %eax , %edx push ー %edx 12 ( %ecx) ,%eax ca Ⅱ * %eax leave て e む 省略 push ー %ebp movl %esp , %ebp $56,%esp sub! $-vt$3Man,-4(%ebp) movl $-vt$3car,-16 (%ebp) movl, $ ー v い 3Dog 广 28 (%ebp) movl $-12,%esp addl ー 12 ( %ebp),%eax push ー %eax ca Ⅱ MoveCa Ⅱ er——FP5Moves $-12ßesp addl 引 sizeof ( man ) より大きなメモリが確保 される。これがテーブルへのポインタ man: 省略 movl man 十 8 ,%edi 省略 movl $man,base—ptr ad 引 $-8 ,%esp pushl $O movswl 8(%edi) ,%eax addl $man ,%eax pushlu %eax movl 12 (%edi) , %eax call *%eax 省略 ーテーブルアドレスが取得される ・ V = STOP; : this ポインタ ー再計算 テープルポインタ取得 base—ptr 第 &man ー ー v STOP ー新土 s ポインタ計算 ー move ( ) のアドレスを取得 、 MASM などでの ca 日 dword ptr [eax] maln: グローバル man の実体の別メモリ部分に ーテーブルアドレスがコビーされる ー offset Size 6 10 8 12 12 16 ←齟 n. move ( ) を指す 20 24 movl $—vt$3Man,man 十 8 省路 -vt$ 3Man : .value 0 . va 1 ue 0 tf 3Man ong . value 0 . value 0 コ ong move—3Mani . ze て 0 ー 8 省略 move——3Mani push ー %ebp movl %espr%ebp ー eave て e セ ースタック上の ・仮想関数テーブルの初期化 Man. move() の実体 れていません。極端な例ですが , やはり C + + で出力コードの最適化を行うことはか なり難しいようです。 グローノヾルオプジェクトでは正しい処理 が行われているようなので , 少しプログラ ムをいじってローカルなオプジェクトにつ いても同じように見てみましよう。 List 6 がその結果です ( コンパイル結果は できる環境になりつつあることも確かです。 次回は List Ⅳ ) 。ローカルオプジェクトについて 最後になりましたが , この Moves クラス も仮想関数のテープルがくつついて初期化 のように必ず派生させることで利用し , そ C + + のバーチャルな関数は , ポインタの されていることが確認できます。 のインスタンスを作成してほしくない場合 変換と密接に結び付いた利用法のための機 仮想関数を持ったオプジェクトにはこの は仮想関数で , 構であることがわかりました。さらに , C + + ような初期化部分が必ず発生するので , 仮 virtual move int f = STOP) コンパイラにはこのバーチャルな関数のた 想関数を持たないクラスに比べてメモリや と記述します。 C プログラマにとってはな めにテープルを用意するという巧妙な機能 実行速度でオーバヘッドが大きいのは確か んともいいがたい表現ですが , このような があることもわかりました。ところで , C + + です。 初期化子を持った仮想関数があるクラスは にはバーチャルな関数だけでなくバーチャ しかし , 現在のプログラマが置かれてい 直接実体化することができなくなります (L ルなクラスも存在します。次回はバーチャ る状況ではこのようなオーバヘッドは無視 ist 7 , Fig. 1 ) 。 ルなクラスについて見てみましよう。 Fig. 1 発生するコンパイルエラー ln function 土 n セ main( . test•cc: . cc : 48 : cannot dec lare variable 'm' セ 0 be of type 'Moves test . cc : 48 : since the following virtual functions are abstract: test test.cc:16 : void Moves : :move(int = STOP) 63 実践 C + + ゼミナール

8. 月刊 C MAGAZINE 2001年12月号

し , データベースをオープンします。デー タベースハンドルは「 $DBH 」という大文字 の変数でグローバルに工クスポートされ , 以下継承関係のあるパッケージで任意に利 用可能になります。ユーザは , データベー スのオープンを明示的に行う必要はありま せん。単にパーサ起動のスクリプト内でこ れから作成しようとする CGI がデータベー スを利用することを指定するだけです。 こうして挿入された ProjectDBLogic にデ ータベースのアクセスルーチンをまとめて 記述することで , 主に CGI の HTMLbody タ グ部分を実装する Pr 可 ect し gic 内でこれら のデータベースアクセスルーチンを呼び出 すことが可能になります。あとは , データ べースを利用しない CGI を作成するのとま ったく同じです。 サプルーチンの作成にあたっての要点は , モジュール化を徹底することです。データ べースのアクセスでモジュール化を徹底さ せるのは , 初心者には少し難しい側面があ ります。参考書などに書かれているのは , データベースをオープンし , 読み書きをし , クローズするという一連の動作だけで , れだけでは複雑なデータベースアクセスを すべて書くには不十分でしよう。もちろん 不可能ではありませんが , 仕組みが大きく なればなるほどコーディングは複雑になり , データの流れもわかりづらくなります。 データベースアクセス サンプルの作成 こでは参考のために簡単なデータベー スを作って , このモジュール化の見本を提 示してみることにしましよう。 データベースのアクセスで必要不可欠な のは , レコードを 1 っ取得して表示するこ とと , 一連のレコードを表形式で取得する という 2 つの事柄です。データの作成や更 新といったものは , 通常連続的ではあって も 1 レコードずつ繰り返すことで可能にな ります。しかし , 複数のレコードの取得を モジュール化するにはちょっとしたテクニ ックが必要になります。 一般的には , 1 レコードを取得し , それ 44 C MAGAZINE 2 1 12 を表示して , 次のレコードを取得して表示 するということを繰り返せばいいのですが これでは表示とデータの取得を分離してモ ジュール化することができません。 WEBM AKER でデータベースをサポートする CGI を 作る場合でもこういったロジックを組むこ とは不可能ではありませんが , あまり推奨 はしません。システムが大きくなったとき に再利用性が極端に小さくなり , コードの 見やすさも失われてしまいます。ケース・ バイ・ケースで必要になることがあるのは いうまでもありませんが。 このことを実現するために , 複雑なデー タ構造を PerI で記述する方法を知らなけれ ばなりません。連続した一連のデータは , 通常二次元の配列で取得可能です。三次 元 , 四次元がないとはいえませんが , 二次 元配列が利用できればその応用ですぐに理 解できるでしよう。 また , WEBMAKER では POST されるパラ メータをすべてハッシュ形式で保持します。 このハッシュを利用すると , どんなデータ が扱われているのかがわかりやすくなりま す。さらに Perl の DBI パッケージにはデ ータをハッシュ形式で所得する関数が用意 されています。このことから , もう 1 つのデ ータ構造としてハッシュおよびハッシュの 配列が扱えれば , ほぼどんなデータベース ルーチンにも対応が可能になるでしよう。 叫 ( $id ) sub get—name—from—id { 1 id から名称を取得するサブルーチン List ・ す。 ・データベーステーブルの作成 まず , 簡単なテープルを作ることにしま CREATE DATABASE sampl edb; CREATE TABLE samp セ ( name age SERIAL PRIMARY KEY VARCHAR, INTEGER, VARCHAR DEFALUT 'ma ー e GRANT ALL ON samp 厄セ b ー to nobody; GRANT ALL ON sampletbl to developper; 記述は標準的な SQI 購文ですが , Postgre SQL をターゲットとしています ( データベー スソフトによっては記述方法が若干異なる 場合がある ) 。 sampledb は , sampletbl とい うテープルを持ち , 名前 , 年齢 , 性別のデ ータ項目を持ちます。名前は文字列 , 年齢 は整数 , 性別は列挙型で male か female のど ちらかの文字列を持ちます。このほかデー タがインサートされた日付を自動的に登録 するように , PostgreSQL の関数 'NOW' をデ フォルトとする regtime という登録日のデー タ項目を追加しました ( そのほか , データ べースのインストールや SQL の利用法につ いては参考文献などを参照 ) 。 E 佩 ( sex in ( 'male 'female' ) ) , regtime DATETIME DEFAULT TEXT 'NOW' my $s 印 = 物 S 新 ECT name FROM sampletbl WHERE id = ? ″ 1 レコードをハッシュで取得するサブルーチン return $$value{name); $sth->finish( my $value = $sth->fetchrow—hashref; $sth → execute ( $id 叫 $s = $DBH->prepare( $sql $$data = $value; $sth->finish ( my $val ue = $sth->fetchrow-hashref; $sth->execute( $id 叫 $sth = $DBH->prepare( 印 my $sql = ″ S 新 ECT * FROM sampletbl WHERE id = ? ” 叫 ( $data, $id ) = ・ gub get—samp 厄 t -from-id {

9. 月刊 C MAGAZINE 2001年12月号

C 卲 P 卲叭ッケージの活用 002 #<input type="hidden" て初期インストール時点に戻ります。 name="dbactn va Iue="REGIST"> 必要な場合は , 別のティレクトリに保 く input type=nsubmit" 存するなどの作業を行ってください。 name=nsubmit" value=" 登録する” > ファイルなどからデータを取り込みペー く input type= reset ジ上に表示させる場合も同様に行います。 name="resetn value=" リセット” > つまり , アンケートの入力画面から たとえば , List 9 のようにします。 cl ose ( OUTPUT 新すボタンをクリックすると , TESTPAG といったふうに記述します。ファイルのア WEBMAKER は , ユーザ定義関数の戻り E 画面へ行くように指示し , TESTPAGEVX クセス権などを適時設定してください。ま 値にセットされた値を body タグの出力とし ージプロックではタイトルなどを指定した た , HtmlE 仼 or にはほかにもいくつかの Web て扱います。したがって , WebA0 ージに何 後 , ユーザ定義関数を使うことをパーサに 出力ルーチンが実装されています。詳細は かを表示させたい場合は , 一連のファイル 知らせます。そして , 上のように Sample man ページを参照してください。 WebMake からのデータなどをスカラー値 ( この場合 gic. pm での関数内でハッシュの表示ルーチ r. con ト sample を書き換えた後は , パーサを は $ body ) に累積させていきます。 ンを記述します。 起動して変更をシステムに反映させるよう もう 1 つ例をあげておきましよう。ファ testhtml_hash 関数は , ページにどのよう にしなければなりません。 イルに格納されたデータが「コンマ ( , ) 」で区 なパラメータが受け渡されるかの確認に利 切られていて , それを HTML のテープルに $ perl WebMaker-samp 厄 . pl 用できます。この関数が記述された後のロ 表示したい場合は List10 のようにします。 webmaker parser started. ジックはすべて無視されるので , ユーザ定 こでの入出力が理解できれば , どんな sample. cgi already exists. 義関数のどの部分ででも利用でき , その時 データでも表示 / ファイルへの更新などが Sample already exists. 点でのさまざまは変数をベージに表示させ 可能になります。ね ble タグの代わりに selec Sample/Samp leHtml.pma lreadyexists. ることができます。 t などを利用するのも同じです。データベー Samp le/Samp leLogic. pma lreadyexists. Fig. 11 を見ればわかるように スへのアクセスも基本的には変わるところ func T no database app lication. はありません。ただデータベースの場合は , ESTPAGE_BODY にはアンケートからの入 samp le/samp leHtmlItems. pm created. 力がすべてハッシュの各値として渡されま 注意 : パッケージの demo ティレクトリに データの取得などに若干微妙なテクニック 付属の「 clean-sample.sh 」を走らせる す。これらの値をファイルに落としたけれ が要請されます。そこで , 次節以降でデー ば , タベースの場合を解説します。 と , これらの変更はすべてクリアされ ' data. txt my $filename open(OUTPUT, ">>$fil ename") Ⅱ $ERROR ->testhtm l—sca lar( ' output e てて 0 て ' foreach pa て ) { print 0 p ー : $pa て { $-) スソフト (databasesoft) を指定すると , パ ーサは初期起動時に ProjectDBLogic. pm と いうファイルを作成します。このファイル はデータベースアクセスを受け持つサプル ーチンを記述するためのパッケージファイ ルです 同時に , このファイルが追加されるのは , 単にデータベースにアクセスする「ユーザ 定義関数」をわかりやすくまとめて記述す るためだけではなく , WEBMAKER オプジ ェクトの継承関係を新しく生成することも までと同じです。違いはデータベースハン 意味しています。 データベースアクセス WEBMAKER のクラス継承図 (Fig. 10 ) を ドルを取得しなければならない点だけでし C 引の作成 再度見てください。 ProjectDBIngic は Html よう。 蚋 BMAKER でデータベースアクセスを WEBMAKER パーサへ指定する際に , デ DBApp を親とするパッケージです。この Ht 伴った CGI を作成するのも , 基本的には今 mlDBApp はデータベースハンドルを取得 ータベース名 (databasename) とデータベー 特集 2 地域通貨決済システム作成にみる CG レ per いッケージの活用 43 P A R T ァータベースアクセス ここでは , WEBMAKER バッケージを使ったデー タベースアクセスの方法について紹介します。 0

10. 月刊 C MAGAZINE 2001年12月号

最後に , 性別一覧ルーチンで配列を利用 ンティアベースで行われるプログラム開発 , からカウントするためのものです。 した例を 1 つあげておきましよう ( List15 ) 。 マニュアル作成 , 公開サイトの構成等々 , では , 同じように利用方法を提示してみ WEBMAKER パッケージのサンプル CGI 広義の開発作業をするコミュニティに「地 ます (List 14 ) 。 こでは取得したデータか にはデータベースアクセスのデモは添付さ 域通貨」を導入することはおおいに意味の ら HTML の table タグを作るようにコーティ あることだと思います。そして , これがプ ングしてみました。基本的な枠組みを示す れていません。 PostgreSQL や MySQL とい ったデータベースソフトがインストールさ ログラムやコンピュータといった活動領域 ために , データ件数によるべージ表示の制 以外 ( 農業や商業 , 工業 , いろいろなサー れていなければデモがあっても意味がない 御やデータの表示順序の指定などは行いま ビス業 ) の「地域通貨」コミュニティと連動 と考えたためです。しかし , こういったソ せんでした。 するようになれば , 私たちは大きくて多様 フトの準備さえできていれば , 上記のよう いずれにしろ , リファレンスを利用した な 1 つの新しい経済圏を体験できるように なデータベースルーチンをテストするのは これらのルーチンによってデータの取得と 表示は分離され , それぞれが完全に独立し それほど難しいことではないでしよう。 なるでしよう。 緒についたばかりの試みですが , もとよ たサプルーチンとして扱うことが可能にな 最後に り少数の人間だけでできることではありま ります。 WEBMAKER のデータベースアク せん。何よりひとりでも多くの方のご意見 , セスではこういったコーディングが推奨さ 冒頭で述べたように WEBMAKER は ご協力を私たちは求めています。 れます。 ProjectDBLogic. pm にデータベー 「地域通貨」決済システム Winds のために作 スアクセスルーチンをまとめれば , 再利用 成された独立のツールです。書店に行くと , の可能性を高めることができます。 主な参考文献 さまざまな CGI のサンプル集や入門書が並 ・データの挿入・更新と配列の利用 以下は , WEBMAKER 開発に直接利用し んでいますが , WEBMAKER はそういった 個々の CGI システム全般を統一的な視野の た文献というわけではありません。参考に データの挿入・更新については , 基本的 もとに作成するための手段を提供できるは なる , 入手しやすい文献として , 筆者が個 には上の例で示したデータベースアクセス ずです。簡単なアンケートや掲示板ならお 人的に利用しているものの中から選んだも ルーチンの「 $ sql 」変数に , それぞれの SQL そらくそれほどの手間をかけずに作りあげ のです。 Perl や PostgreSQL などを利用する 文を書くだけです。たとえば , にあたってひととおり目を通しておけば , ることができるでしよう。しかし , 同時に ・テータの挿入 必ず役に立っと思います。 WEBMAKER はまだ完成品とはいいがたい DINSERT INTO samp letbl ( my $sql [ 1 ] 『実用 Pe プログラミング』 , sriram srini のも事実です。この点で読者のみなさんの name, age, sex) VALUES ( ? , ? , ? 尸 vasan 著 , 須田隆久訳 , オライリー ご協力をいただければ , これほどの幸いは ・テータの更新 、ノ ありません。 ヤ / ヾン my $sql = "UPDATE samp 厄 t SET [ 2 ] 『 Pe 日モジュール活用ガイド』 , Eric Fost また , 「地域通貨」というシステムは , 西 name = ? WHERE id = ? ” er-Johnson 著 , アークシンクタンク訳 , といった具合です。最初の例はパラメータ 部氏が述べておられるように , 経済システ に名称 , 年齢 , 性別をとり , その値でデー ムとしてダイナミックな可能性を秘めてい 翔泳社 [ 3 ] 『 Pe 月 5 パワフルテクニック大全集』 , Ste ます。プログラマの方々ならここカらオ タを挿入します。次の例では , 特定の id の プンソースの理念との近親性を感じる方も データ項目 name をパラメータで与えられた phen Asbury, Mike GIover, Aidan Hum おられるかもしれません。たしかに , ボラ 値に変更します。 phreys, Ed Weiss, Jason Mathews, S elenaSol 著 , アクロバイト監訳 , インプ St 配列を利用した性別一覧ルーチンの例 レス [4]fApache 拡張カイド ( 上・下 ) 』 , LinC01n sub get—sampletbl—sex { 叫 ( $data—array, $sex ) = 釭 Stein, Doug MacEacbern 著 , 田辺茂也 , "SELECT name FROM sampletbl WHERE sex = ? ″ my $sql my $sth = $DBH->prepare( $sql 田和勝訳 , オライリー シャノヾン $sth → execute ( $sex my @array; 全攻略ガイド』 , 石井達 my $idx = my $num—rows = $sth->rows; 夫著 , 技術評論社 while ( my $values = $sth → fetchrow ) $ 矼て a ⅵ $i + + ] = $values; [6] 『 PostgreSQL 詳解 , 糸魚川茂夫著 , オ $sth->finish( ); $$data—array = *@array; ーム社 return $num—rows ー 1 ー [ 刀『 MySQL 徹底入門』 , 日本 MySQL ユーザ 会著 , 翔泳社 46 C MAGAZINE 2001 12