kr_ryo 徒然日誌 <2005年8月28日分>

三國志製作記111〜オブジェクトをポインタで乗りこなす!〜

4周年を無事迎えた我が〔日曜プログラマーの部屋〕kr_ryo's Home Page、三國志製作記も第111回となかなか縁起のいい数字(^^;;台風一過の後のさっそくの秋のイネ科の花粉症に鼻をぐずぐずいわせながら、オブジェクトをどんどこ作っています……いや、実は、どんどこ作るよりも、作ったばっかりのオブジェクトをどんどこどんどこ改良改良を進めていっています。

さっそく技術的な話を(^^;;たとえば武将というオブジェクトは、もともとの型となる武将クラスを作って、個別具体の張飛オブジェクトだとか、関羽オブジェクトだとかいう武将インスタンス(=実体)を生産していくことになります。しかし、たとえば武将が実は1人しか必要ない場合であったら、せっかくの武将クラスも、1つのオブジェクト(=インスタンス)しか作る必要がありません。たい焼きの型を買ってきたのに、1個しかたい焼きを作らないみたいなもんです。そのこころは、もったいない(^^;

オブジェクト指向でプログラムを作ると、なんでもかんでもオブジェクト化したくなるようでして、実際に作られるインスタンス(=オブジェクト)が1つしかなくてもクラスを書いてオブジェクト化させたりします。いろいろネットや本などでオブジェクト指向設計の例を見ていると、どう考えてもインスタンスが1つしか必要ないものもオブジェクト化していることが多く、もったいない気がしてきました。

ところで、私がオブジェクト指向に興味を持ったのは、1人しかいないどころか、100人1000人の武将どもを片手で扱いたいがためです( ̄^ ̄)従来型手続指向型プログラムでは気が狂いそうになるくらい分岐を繰り返しそうな大量武将を扱う処理を、オブジェクト指向ならば簡単に処理できそうだ、そこが大きなポイントです。

今のところ、その期待は裏切られず、データベースよりも処理しやすい、という気がしています。まだまだ精進は必要だな、という気がしてはいますが……その精進の必要が早速訪れました!( ̄□ ̄;)

大量の武将インスタンスを生産したとしても、必要な武将をすぐ取り出せなければ意味がありません。1000人からなる武将オブジェクトをどうやって、そいつ、あいつ、と指示できるか、というと、前回の4周年記念特別企画でも振り返りお話したように、リスト表示を使いたいと考えています。エクスプローラで「詳細」表示してでる、あれです。

ただ、このリスト表示、昔クリックして選択する挙動がおかしい、ということでえらく苦労したことがあったとお話しましたね。その時も根本は同じ問題で、何が苦労するかといって、一覧表示させて、誰それを選択したとしても、プログラム側で得られる情報は、誰それ、という名前の文字列だけなのです。つまり、確かに表示の際には、いろんな情報を一覧で表示してくれますが、そこに表示された情報はあくまで文字であって、それと表示元とは、文字列が同じ、というつながりしかないのです。

具体的には、関羽オブジェクトを表示させたとして、関羽という名前、武力や所在地といった情報を関羽オブジェクト側からリスト側に渡して文字で表示させたとしても、関羽が選択されたリスト側からプログラムに対しては、誰かが選ばれたことと選ばれたのは関羽という文字、ということしか情報がないのです。ここから、選ばれたのは関羽オブジェクトなんだな、と判断するようプログラムを書かなきゃあなりません。

世に出なかった前バージョン三國志(^^;;のデータベース中心型では、関羽という文字列を武将データベースから探せ、とデータベースに命じるだけで済みます。劉岱のように同姓同名同字な奴が2人いたら困りますけど、たいていは1人だけヒットできます。この辺はデータベースの得意とするところで、プログラマーはそれ程考える必要はありませんでした。ただ、データベースだと、他の部分は手続指向型のまま。これ自体がプログラマーの負担なのです(~_~;)そこでオブジェクト指向へ、というのが4周年記念特別企画で振り返ったところです。

もちろんデータベースの世界も、現在のリレーショナルから将来はオブジェクト指向データベース、とも言われており、実用化されているものもありますけど、まだそこまでいっていないDelphiのオブジェクト指向、それでもデータベース並に大量の情報を扱えると期待して望みました、が、違いました(~_~;)

オブジェクト生産、クラスからインスタンスを大量生産したとして、それだけではプログラマーがどのインスタンスを指せばいいのか、実はわかりやすい方法がありません。データベースなら武将の名前もよいですけども、通常はID番号を準備して、その番号でいろいろ指し示すことになります。しかしオブジェクトは、作ったら作りっぱなし状態に陥り、適当な指し示す方法がありません…いや、実はあるんです。それがポインタと呼ばれるものです。

ポインタ。C言語を少しだけかじった(^^;;私でも、ポインタが理解できないとか、ポインタと構造体がC言語の悩みの種だとかいう話をよく聞きました。なんだかおどろおそろしいもののようだ、という印象だけが残っています(~_~;)その後オブジェクト指向で、オブジェクト同士の関連を扱うのにポインタを使う、と聞いた時、むむむ、と思ったものです。そしてオブジェクト指向でプログラムしたとたん、やっぱりポインタ、というものが必要だ、と気付くにいたったのです( ̄□ ̄;)

このプログラマー恐怖の的、ポインタとはいかなるものか、ご説明しましょう。コンピュータは究極的には2進数、0と1、オンとオフで動いています。CPUもメモリも元をたどればトランジスタというちょいと複雑なスイッチの超超巨大な固まりでしかありません。で、その01000110101011…というひたすらの2進数は、人間にはちんぷんかんぷんでも、コンピュータには意味を持っていて、というより、それに意味やルールを人間が持たせているから、それをもとに動いているのです。

だから、人間、いや、コンピュータにとっても適当に切ってしまったら、10011001010…という羅列は単なる羅列にすぎなく、なんのことかわからないのです。だから、ルールさえわかれば、人間でも言っている意味がわかります。さすがに2進数では量が多くてめんどうなので、変換して16進数、0〜9とA〜Fで表しますけども、これがいわゆるマシン語、私も昔は、雑誌の付録プログラムで、延々10 5B 7C 22 23 …という英数字の羅列を、軽く何ページ分も打ち込んだことがあります( ̄^ ̄)

そういう環境から見れば今のDelphiなんてなんて作りやすいんだ(^O^)と感激してしまうわけですけども、それはおいておいて(^^;;コンピュータは、メモリ上にプログラムやデータを2進数で羅列して保存しています。それはきちんとルールで決めた数で一区切りということにされています。その一区切りがどの一区切りかと、指すのがアドレス。そのアドレスが入った変数がポインタ、というわけです。

まだ分かりにくいでしょうか?メモリ上には01000110101011…という羅列が並んでいます。それを16なら16、32なら32ごとで1区切りと決めるわけです。その1区切りを、一番最初から順番に名前をつけているのがアドレス。アドレスは10進数の数字、4丁目とか22番地とかいうわけではなく、それ自体コンピュータに都合のいいように2進数ですんで、010000101…と人間様には扱いにくい。それでそのアドレス自体の中身は知らなくてもいいので、変数として扱えるようにしたのがポインタなのです。

要するに、プログラマーが直接コンピュータの2進数のメモリを触りにいくためのものがポインタです。そりゃあわかりにくいのも当然で、必要がなければなるたけ使いたくないわけです(--;プログラムはわかりにくいと言われていますけど、それでも2進数010000101…よりははるかにましで、16進数105B321F…ならどうよ、とかいわれても、、、ですわな(~_~;)数字で命令するより英語で命令する方がはるかにましで、それがいろんなプログラム言語というわけです。

それでも直接メモリを扱いたい、つまりポインタが必要な時が来ます。それがC言語でいえば構造体、オブジェクト指向言語でいえばオブジェクトです。話を前の方にずーっとひき戻すと、オブジェクトを生産すると、オブジェクト相当分の情報がメモリに書き込まれます。どんどん生産すると、どんどん書き込まれていきます。関羽オブジェクトと張飛オブジェクトは順番に、ある番地のアドレスに、クラス設計した内容のとおりメモリを準備し、その中に数値や文字や関数が書き込まれていきます。こっからがあるオブジェクトだよ、というアドレスを指定すること、これがオブジェクトを直接指定する方法なのです。そして、そのアドレスを変数として格納するのが、ポインタなのです。

もちろんポインタで指定せず、オブジェクトごとに違う、kannuだとかtyouhiだとかの変数を作ることも考えられるわけですが、それだと武将として何かをさせたいときに、誰に命じているのかをいちいちプログラムで書かなければいけません。まさか1命令ごとに、関羽→攻撃、張飛→攻撃というように全員の変数を並べ立て、誰が対象かによって分岐させる…そんなおそろしいことはようできまへん(~_~;)だいたいそうすると、攻撃対象も全員の組み合わせ分を作らなければならず、あっと言う間に破綻です。

そうしないためには、個別の変数でなく、武将オブジェクト総体として考えるべき、ということになります。で、Delphiなどオブジェクト指向言語には、オブジェクト、というよりポインタ管理用のリストというオブジェクトがあります。先程の目に見えるリスト(正式にはTlistviewというクラス)ではありません。単なる1列リストです。これは目に見えるわけではないんですけど、ポインタを順番に格納していけます。リスト(正式にはTlistというクラス)にオブジェクトを作る毎に格納していけば、最初から順番にインデックスという数値でそのポインタを指示できます。たとえば関羽オブジェクト、張飛オブジェクト、孫幹オブジェクト…と順番に生産して格納していけば、最初の0は関羽で、順番に、1、2と、インデックス数値で管理できるのです。2なら孫幹ですね。

最初このインデックスというもので管理するので、まあ、言ってみればデータベースのID番号、数値管理ができるので原始的なデータベースの発想でプログラムを作るものと考えていたのです。要するに、見えるリスト(TlistView)で選択した関羽という文字列を、ポインタ格納武将リスト(Tlist)で1から順番に探していくわけです。ただ、見えるリストは武将一覧であったり、城一覧であったり、いろんなものを一覧表示するわけですから、これを毎回毎回ぶんぶんまわして(=繰り返して)調べるプログラムは、いかにも手続指向型プログラム、これをオブジェクト指向風にするには…

調べられるオブジェクト側で探すプログラムを書けばいいのです(^-^)この発想がなかなかできない、できればオブジェクト指向的ともいわれるらしいですが、慣れるとプログラムが異様に簡単になる、というのは以前お話したところ。ただ、オブジェクト側での作った内容が、まだオブジェクト指向でなかったのです(~_~;)というのは、見えるリストで誰かを選択したら、直接探すんでなく、探されるオブジェクトの関数の、誰なのか探す、という関数を呼び出します。この関数では、やっぱり格納リストを1件1件しらみ潰しにお前は関羽という文字列を持ってるのか?と訊いてまわるわけです。で、見つかったら、そのリストのインデックスを次の処理に回す、というようなことをしていたわけです。

それで、関羽はある勢力に属する、というようなことを記録したいと思います。関羽は武将リスト番号3、劉備勢力は勢力リスト番号2とか、リストに入れる順番で決まっています。で、ある勢力には誰が属しているか、とか、関羽はどの勢力に属しているか、というのを見たい場合は必ずあります。その際、関羽という武将オブジェクト側にどの勢力に属しているかという情報を持たせるのか、勢力オブジェクト側にどの武将オブジェクトが属しているかという情報を持たせるのか、という問題があります。もちろん両方が持っていれば、使うとき探すときは楽です。しかし、関羽が勢力から離れたり死んだりしたときに、必ず両方の情報を変えなければなりません。それができなければなかなか見つからない矛盾、バグが生じるのです。逆に、どちらか片方しか情報を持っていなければ、情報が無い方での処理は、いちいち探す、ということになってしまいます。

それくらい、オブジェクト同士が関係あるオブジェクトについて持つ情報はプログラム上大事なわけで、実際これを矛盾なく処理することばっかり書いているわけです(~_~;)で、その大事な根拠となる格納リストの数値、これは作って入れた順番になります。これだと、やや、不安な感じです(*_*)というのも、じゃあ、間に入れたら番号はどうなるのか、とか、間が消えたら番号はどうなるのか、ということがあるからです。

その辺はちゃんと考慮されていて、通常リストには常に格納された順番に入れられ、間を消しても単に空き列になるだけで、番号は変わらない、ということになっています。もちろん逆に、直接上書きで書き換えたりだとか、間を完全に消すと同時に後ろの番号も繰り上がる、という操作もできます。そうなると番号が変わっちゃうわけで、なかなかおそろしいわけですけども( °O °;)その代わり空き列分無駄なメモリを解放できます。武将なんかはほとんど変わらないでしょうけど、それでも死んでもリストには残るのがどうか、という問題が生じます。それよりも、軍団なんかは、作っちゃあ負けて消え、作っちゃあ参加武将数が増え、その上思いつきで解散させたりすることもできる予定です。そうすると、空き列がどんどん増えていくわけですね。空き列を完全に消してしまえば、そのたびごとに全部の武将の参加軍団ID番号を変えたりする処理が生じます。うーん、なんだか困ったことになりそうです(~_~;)最近の大量にメモリが搭載された高速のパソコンなら多少空き列が増えても全員のID番号を変えてもどうってことはなさそうですけども、なんだか泥臭い処理です。何よりプログラマーが負担だったり美意識に反したりします(^^;;

データベース的発想ならば、管理リストのインデックス以外に、独自にID番号を武将や軍団オブジェクトに持たせるのが妥当と思いつきます。ただ、指摘するのは管理リストのインデックス番号なんであって、オブジェクトの持っているID番号で管理リストのインデックス番号を見つけるのと、オブジェクトの名前文字列で同じことをするのと、どちらもリストをぶんぶんまわして確認するんであって結局同じです。いずれにせよ、スマートではない…ここで思索に耽ります……オブジェクト指向では、どうやって解決するか、というより、なぜ、そうなのかを考える方が有効だそうです………なぜそうなるのか?

要するに、管理リストが管理しているのは、ポインタなのです。オブジェクトはポインタで管理できるのです。むしろオブジェクトとはポインタそのものともいえます。管理リストは、ポインタたるオブジェクトを順番にインデックスをつけて並べているだけです。オブジェクトに改めてID番号をふっても、その番号と管理リストとはなんの関係もありません。また、管理リストのインデックス番号が変わっても、格納されているオブジェクトたるポインタは変わりません。インデックス番号とオブジェクトとも、関係はないのです。……!!

そうです、オブジェクトとオブジェクトとの関係は、直接相手のオブジェクトたるポインタを持ち合えばいいのです。最初にそう書いていたじゃあないですか、オブジェクト指向では、関連を扱うのにポインタを使うって…!( °O °)

ここまで来れば後は簡単。オブジェクトはポインタとして扱えるわけですから、管理リストから直接ポインタのままオブジェクトを受け取り、そいつに命令すればいいのです。いちいちリストから該当するインデックスを探し出すのは同じながら、探し出したインデックスをもとに、さらにオブジェクトに対する命令の中で、インデックスを指示したリスト中のオブジェクトに対する命令…という複雑なこと(文章で書けばますます複雑ということがわかる複雑さです(^^;;)をせずに、探し出したうえ(探すのはオブジェクトの仕事)でそのまま命令をする、というきわめてスマートで簡単に命令が組めるようになりました(^O^)これもポインタをわかり、ポインタとはオブジェクトそのものとわかったからですね〜!怖い、わからんとされたポインタもまるでまんじゅうをこわがってたようなもの、オブジェクトを直接触れる非常に便利なものです。まさに、必要があってわかる、というものです(^O^)

今回非常に技術的な話になってしまいました(^^;;しかも、この理解のおかげで、これまでひとつひとつ積み上げてきたオブジェクトに対する命令一式が全部ポインタ利用型に書き換えになってしまいました(^^;;クラスを大量生産するどころか、最初に作ったオブジェクトを作りなおしています。しかし、まあ、この先楽ができるとわかる、いい技術です。ほんとに(^^;;;

index

〔TopPage〕

このページへのリンクはフリーです。
このページについてのご意見、ご質問などは、kr_ryo_green@yahoo.co.jpまでお願いします。
Copyright 2005© kr_ryo All rights reserved.
訪問件数