第C1回 壁紙変わったけど(なんやったんや..) 00/08/22

なぜか、うちのブラウザはよく落ちる。決して、エロサイトを巡回しているからではないはずだが。(ってわざわざ書くと余計あやしい..^^)

ランタイムエラーが発生しました。デバッグしますか?

とか聞かれてもやなぁ、誰がそんなもんデバッグするっちゅーねん(笑) それもディフォルトで、「デバッグ」になっているところが憎たらしい。俺にそこまでしてデバッグさせたいんかな〜。('00/09/13追加 : 「ツール」>「インターネットオプション」の[詳細設定]タブの中の「ブラウズ」項目下にある、「スクリプトのデバッグを使用しない」をチェックすることで回避できるようです)

それはともかく、壁紙の変更だが、レジストリいじるのだとばかり思い込んでいた。あまりに簡単すぎるのかサンプルを載せているホームページなんか見当たりゃしない(笑) この手の入門書を持っていない、やねうらおは、こうなりゃ勉強させてもらうか〜とばかりに壁紙チェンジャーソフトをかたっぱしからダウンロードして、逆アセした。

わかったこと。レジストリをいじったあとにSystemParametersInfoしているソフトもある。いじっているレジストリキーは、"Control Panel\Desktop"の"TileWallpaper"のようだ。しかし、いじっていないソフトもある。そんなこんなで::SystemParametersInfoの呼び出しパラメータを調べてたら、どうも3になっている。つまり、

#define SPIF_UPDATEINIFILE 0x0001
#define SPIF_SENDWININICHANGE 0x0002

3 == SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGEですか?

 ::SystemParametersInfo(SPI_SETDESKWALLPAPER, // 壁紙の変更
  0,(void*)lpszFileName,SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);

つまりは、これで良いようだ。Windows95になったときに変更になったのかも知れない。もう〜Dr.GUIの嘘つき〜(笑)

いい加減、Windowsプログラミングの入門書買ったほうがいいかも知れない^^ >私

('00/08/24 追加記事)

bmdさんから壁紙に関して、詳しい情報をいただきました。ActiveDesktopまわりのことまで報告があり、きわめて資料的価値が高いと思いますので、bmdさんの許可を得て全文を紹介させていただくことにします。bmdさん、ありがとうございました。しかし、Microsoftのサイトにこの情報が無いのは何故なんだろう..^^;


第C2回 L1からパージされるペナルティ(は皆無か?) 00/08/27

どうも、ンとソを入れ替えて書くのがちまたで流行っている。どういった言語現象なのだろうか。私が5年ぐらい前にびっくるネットでやった記憶はあるが、インターネットを通じて若い世代に社会現象(?)として広まっている(私が発信源だと言う気は毛頭ありません)のは、とりあえず、不思議な気分である。顔文字、アスキーアートにも代表されるように、文字を単なる記号としての用法を超え、視覚的に訴えようとするのは文字コミュニケーションの1つの進化形なのだろう。たとえば、スンマソンならば、スソマンソ。応用としては、平仮名でもOKなのだろうか?→すそまんそ 流石に平仮名では何のことだかわからないが、今後流行るかも知れない。(全然流行らないかも知れない^^)

2CHに、rsp(この連載)の叩きコーナーが開設された。まあ、開設された日付から言って、坂田君の遠吠え(毒電波観測所も参照すること)なのだろう。まあ、ほとんどは匿名性を利用した単なる誹謗中傷である。誹謗中傷に関しては有名税とでも考えて無視することにしている。ただ、技術的な内容として取り上げる価値のある意見も1つ、2つ混じっているので、今回はそのうちの1つを取り上げる。

32 名前: >31 投稿日: 2000/08/24(木) 22:44
お疲れさまでございます
へなちょこ・フォン・やねうらお男爵
是非、オールアセンブラで書いた3Dプログラムを公開していただけませんでしょうか
除算命令を用いず、いかに超高等テクニックで記述されているのか期待してます

それから質問なのですが、アプリケーションを2つ走らせると10倍遅くなるそうですが、
どんなWINDOWS環境をお使いでしょうか?
まるでジョブ切り替えのタイムスライスが1命令ごとに起きているかのようです
スーパープログラマであらせられるへなちょこ・フォン・やねうらお男爵でしたら、
順位が通常のスレッド約20msオーダーで切り替わるはずのOSを改造して、
そのようなことも可能なのかもしれません
そんな想像をうち砕く超必殺技でもあるのでしょうか
いやいや、私のような厨房ごときが太刀打ちできるはずがありません

第BE回の内容についてのようだ。「順位が」とあるが、プロセス順位の変わる間隔がジョブ切り替えのスパンと、どう関係あるというのだろうか?独り善がりな日本語で何を言いたいのかまったくもってわからない。いま問題としているのは、ジョブ切り替えのスパンではないのか?どうも、プロセス順位の切り替え間隔=ジョブ切り替えの間隔と勘違いしているようにも読める。それが、20msでしか切り替わらないならば、どうやって60FPS(秒間60フレーム)のゲームを実現すると言うのだろう?(20ms×60FPS=1200ms > 1秒) 私が除算命令の仕様を知らないとか何とか難癖つける前に(これについて詳しくは、次回に譲る)、自分が小学校に行って掛け算の勉強をしてきたほうがいいのではなかろうか?

timeGetTimeの結果を連続して取得してみればわかると思うが、1ms間隔である。(NT系ではtimeBeginPeriodで分解能を1msに設定しておくこと) つまり、少なくとも1ms以内に、他のタスクをまわって、またもやCPU時間が割り振られているということである。実際に、このようなアプリを複数実行してみればわかると思うが、1ms以内に再度、CPU時間が割り振られている(マシンが重くなってくるとそうでもないが) バックグラウンドであまたのタスクが動いていることを考えると、タスク切り替えは秒間数万回近く行なわれているはずであり(→すそまんそ。そこまで実験してません^^ もう少し少ないかも)、2つのアプリがメモリバスに強烈にアクセスしつづけて、タスク切り替えのごとにL1キャッシュの内容が完全にパージ(追放)され続けたらどれだけのペナルティを食うだろうか?

ちなみに、486のCPUダイアグラム(Pentiumのものは持ってません。すそまんそ^^)を見る限り、MMU(MemoryManagementUnit=アドレスを実効アドレスに変換する)はCPUに内臓されている。L1キャッシュもCPUに内臓されているだろうが、位置的にはMMUの“外”にあるのだろう。そうでなければ、プロセス切り替えに際してL1をフラッシュしなければならないことになる。さすがにそんな馬鹿な設計はしていないだろう。

L1キャッシュが仮に256KBあるとする。本来、L1に収まっていればノンペナルティでアクセス出来る。それが、強烈なメモリ転送でタスク切り替えのごとにL1から完全にパージされ(話をわかりやすくするため、L2からもパージされるとして)、それが秒間1万回発生するならば、256KB×1万回のメモリ転送のコストが余分に発生する。つまり、秒間256KB×1万=2560MBのメモリ転送の追加コストである。これが無視できるオーダーなのだろうか?

実際にこんなシナリオ通りになるのはレアケースだと思ったあなた、それ正解(笑) 通常のプログラミングではほとんど考慮しなくて良いだろう。また、二つ同じアプリを起動して2倍以上の速度になってしまうというのは、資源を共有するので同期プリミティブとかで待たされるほうが大きいだろうし、アプリがメモリに入りきらなくなってスワップしていたら、それどころの騒ぎではない。私だって、いくらなんでもそれくらいのことは理解した上で、10倍遅くなるのくだりは無責任発言として冗談として書いているのだが…。何にせよ、冗談が通じていないようで、すそまんそ。(やっぱ、すそまんそはやめよう。わかりにくい(笑))

('00/08/28 修正記事)

私は、今回の記事で重大なミスを犯していることに気づきました。測定のために呼び出しているtimeGetTimeですが、このようなWindowsのAPIを呼び出した瞬間に、次のプロセスにタスクスイッチが切り替わるようなのです。あちゃ〜。俺って馬鹿〜。そこで、氏が指摘している、「通常(WindowsAPIを呼び出さないときは)、20ms程度でタスクが切り替わる」というのは正解です。これについては、初心者の方の混乱を誘発したと思われるので、氏ならびに読者のみなさんに、謹んでお詫び申し上げます。

ただし、逆に、通常の行儀の良いプログラム(?)では、定期的にWindowsAPIを呼び出すと思われるので、プロセス切り替えが、秒間数万回行なわれる(ている)という仮定自体は、非現実的なものではないと思われます。実際、そういう状況は簡単に作り出せます。そういう状況においては、やはりL1からパージされるペナルティは無視できないこともあると思います。

また、L1はMMUの外に置くと、MMUでのTLB部分がボトルネックとなりうるので、L1は外側とは限らないようです。K5、K6のCPUダイアグラムを見る限り、L1は内側にあります。ただし、内側に置いてしまうとプロセス切り替えに際してL1がフラッシュされるので、巨大なL1が無駄になりうると思います。K6では2way set方式(詳しくは書かれていませんでしたがTLBのエントリを覚えておいて、プロセス切り替えに際してL1のフラッシュを避けるんでしょうか?だとすれば、私が第BE回で書いたようなプロセスディスクリプタを用意するよりも、はるかに優れた方法です)を採用しているようです。ともかく、L1はMMUの外側にあるという仮定は誤りです。重ね重ねお詫び致します。

上記の結果からすれば、WindowsAPIをほとんど呼び出さないようなアプリを1つ起動すると、そのアプリからタスク切り替えが行なわれるのは、20msのタイムアウトを待たねばならないので、他のアプリはCPU時間がなかなかまわってこず、非常に迷惑するということは有り得そうです。2つのアプリを起動すると10倍遅くなる(?)ってのは、むしろこのへんに由来するものなのかも知れません。

('00/08/28 修正記事 20:27)

↑のを書いてから、やっぱおかしいな〜と思って、実験やりなおしました。案の定(?)、timeGetTimeでは、プロセス切り替えは発生していませんでした。ぴったり20msでタイムアウトになって、次のプロセスに切り替わってました。自分のライブラリのなかで暗黙的にSleepしてる箇所があって、そいつがスレッド切り替えさせていたのでした。(マヌケな話やのぉ..) よって上の「プロセス切り替えが、秒間数万回」のくだりは完全な嘘です。重ね重ねお詫びいたします。

って、これ書こうと思って、インターネットに接続したら、間違い指摘メールが山のように来てるし(笑) ごめんよ〜俺が悪かったよ〜。もう、いまの『Revolution』の仕事が終ったらWindowsから手を引くんだから許してくれよ〜(笑)

しかし、今回の2CHで指摘してくださった氏には、悪いことしました。いろいろ、いい勉強になりました。この場をお借りして感謝の意を表したいと思います。本当にありがとうございました。


第C3回 インテリさんは危険(何のこっちゃ) 00/08/31

たとえば、知り合いにプロのグラフィッカーを紹介してもらうことがあります。それで実際に会って、今度、一緒に仕事しましょうだとか、会社やめますんで今度はそっちで仕事させてくださいだとか、外注として仕事引き受けたり、コミケに出す××を作ってくださいだの何だのお願いして(お願いされて)帰ってきたあと、「今日はお疲れさまでした」の一言だけでも掲示板に書きに行こうと思って、教えてもらったURL打ち込んで、その人のホームページに行くわけです。

そしたら、ちょっとアングラ系のサイトなんですが、何と、そのホームページには、ロリータ観察日記みたいなものがあるではないですか(笑) 近所の女の子を公園でデジカメに撮影する話だとか、幼稚園の運動会に父兄のふりして参加する話だとか(笑) 道ばたに可愛い女の子落ちてないかな〜とか書いてあるわけですよ(笑) おいおい、なんやねんそれ〜。だいたい女の子なんか、落ちてないですよ。落ちてたとしても、あなた、そんなのお持ち帰りとか言って自宅に持って帰ったら、その時点で犯罪じゃないですか(笑) というか、そこの書き込み、全てが、あ痛ててて〜なんですよ。だから、これって見なかったふりしたほうがいいのかなぁ。それとも、やはりこの人とはもう関わらないほうがいいんだろうかとか私はしばらく悩んだわけです。んー。でも付き合いってものがあるしなぁ。この人は、あの人と友達だしなぁ..。とかおかげで、自分の娘の部屋を掃除していたら、ピンクローターが出てきた母親の気持ちになりましたよ。(おいおい、ここは18禁じゃないんだぞ>俺^^)

そんなこんなで、やっぱ仕事の付き合いもあるし、あれは見なかったことにしようと、やねうらおは決意したわけです。というか、見なかったことにするから、お願いだから、その手の犯罪だけは犯さないでね(笑)>某氏

さて、VC++6.0のインテリセンスなのですが、なぜあんなに遅いのでしょうか。HDにアクセスして、一瞬フリーズすることが多いです。そもそもこれだけの時間フリーズさせるためには、単純にデータ量だけで言えば5MBや10MBぐらいは、がりがりと書き込まないとこんなことにはならないと思うのですが、どうすればこれだけ遅くなるんでしょうか?

余談ですが、関連付いていない拡張子のファイルを実行も、やたら待たされます。たかだか、100や200の拡張子、仮に1000種類登録してあったとして、もしその拡張子が全て3文字だとすれば、関連付いているかどうかは、たかだか3000バイトを調べるだけではないですか..。

デスクトップについて言いたいことは、他にもあります。ファイルの新規作成→フォルダでフォルダを作ろうと思っても、新規作成のリストが多いので、新規作成→フォルダは上のほうに表示されてしまいます。そこをマウスで通過しようとすると「表示(V)」だとか「お気に入り(A)」だとかを選択してしまうのです。もう〜(笑) ウィンドゥ管理、もっとしっかりやれー!!と言いたくなります。('00/09/16 フォルダ作成はキーボードからALT+F,W,Fでやってますが^^;>私)

あと、Windows2000ですがDownload中のjpegファイルを縮小表示していると、デスクトップごと落ちることがあります。こうなると何もできません。も〜(笑) 俺のスタートメニュー返せ〜(笑) 仕方ないので、CTRL+ALT+DELで、タスクマネージャを起動して、新しいファイルの実行で、explorerと直接入力して、explorerを実行して回復させてます。非常にまいっちんぐです。(いまどきの世代はマチコ先生なんか知らんて..)

話がそれました。VC++6.0(SP4)のインテリセンスなのですが、バグを発見しました。このような、循環参照するクラスを書いて、

class X : public X {
YY(void);
};

コンストラクタYYの部分をXにペーストで置き換えようとした瞬間に落ちます(笑) とんでもないです。コンパイル中に落ちるならば、わからなくもないですが(いや、それでも納得いかないが..)編集中に、ちょっと書き換えしてて、これで落ちるというのは何なんでしょうか。私は、エディット環境のカスタマイズをしていたときに、知らず知らずの間にALT+F4を、CTRL+Vに割り当ててしまったのかと思いました。(笑) どうも違ったようです。(いくらなんでもそんなことを無意識のうちにしないだろう..^^)

まあ、インテリセンスの発想は買いますが、たいして役にもたたないインテリセンス、大きなプロジェクトだとガラガラとHDにアクセス行ってる間に手で入力できてしまいます。非常にまいっちんぐです。今後に期待というところでしょうか...。


第C4回 今後、2Dの主流はモーフィングになる(かもね^^) 00/09/03

もう死にそうなぐらい忙しいです。確かに、いまやってる外注の仕事(某携帯ゲーム機≠Palm)は、1本売れたら50円のロイヤリティが入ってきて、まわりからは少なくとも2,30万本は売れるんでない?とも言われていますが、人間、お金とかより、生きててなんぼではないんでしょうか(笑) 食事はパンの耳だけでもいいからもっと普通の生活したいです(笑)

今日は、パンの耳に砂糖つけて食べますにょ。ちなみに昨日は、パンの耳にハチミツさんでしたにょ。贅沢は敵ですにょ。明日は、パンの耳のてんぷらですにょ。明後日は、パンの耳にバターですにょ。明々後日は、パンの耳の味噌汁ですにょ。その次の日は、揚げパンの耳のですにょ。パンの耳、パンの耳。おいしいですにょ〜。パンの耳、うまいにゅ。ゲマ〜。

……いかんいかん。変な想像してしまった^^;

そういや、3Dでは、モーションブラーが今後流行るのではないかと言いましたが、2Dではモーフィングだと思うのです。モーションブラーが時間軸に対するアンチエイリアスであるとしたら、2Dで描かれた2枚の絵に対するそれは、まさにモーフィングではないかと思うのです。

そんなわけで実時間モーフィングでどの程度の速度が出るのでしょうか?yaneSDK2ndによるモーフィング(詳しくは、yaneSDK2nd講座のサンプルその2を見ること)ですが、私のマシン(Celeron333MHz)で10FPS程度。一度、モーフィングさせたあと、それをブレンド転送しているのでやや遅い気がします。まあ、現状のものでも十分実用になる速度だとは思いますが、この手のモーフィング専用ルーチンを書けば、この2,3倍ぐらいの速度は出るんではないでしょうか。

つまり、クロスフェードの基本は、
  Dst = Src1 * α + Src2 * β で、α + β == 1
なのですが、小数が出てくるとやらしいので、α,βは256倍して、0〜256を指定するとき、
  Dst = (Src1 * α + Src2 * β)>>8 かつ α + β == 256
これよりβを消去
  Dst = (Src1 * α + Src2 * (256 -α))>>8
     → ((Src1-Src2) * α + (Src2 <<8) ))>>8
     → (((Src1-Src2) * α) >> 8) + Src2
で、何と、掛け算が一回減るのです。というか、R,G,Bに関して掛け算を行なっているので3回分の掛け算が減るのです。これはめちゃくちゃ大きいです。

さらに言えば、掛け算自体をテーブル化して消せます。掛け算テーブルは、各フレームの最初に作っても十分にお釣りが来ます。
ただし、Src1-Src2の取り得る値の範囲は-255〜255なので、テーブルサイズは511必要です。

もちろん、掛け算テーブルを作るのに

for(int i=0;i<256;i++)
 adwMulTable[i] = a*i;

ではなく、掛け算は使わないようにして..

WORD t = 0;
for(int i=0;i<256;i++,t+=a)
  adwMulTable[i] = t;

でしょうか。いや、DWORDにして

DWORD t=(a<<16),as=(a<<1)+(a<<17);
for(int i=0;i<128;i++,t+=as)
  *((DWORD*)&adwMulTable+i)= t;

とか?(笑) いや、ダウンカウンターにして、adwMulTable+iをやめたほうが..まあ、こんなところはどうでもいいのですが^^; ブレンド関数のなかでは前回のα値と違うとテーブルを作り直すので、なるべく高速なほうがいいかなーと思いまして..。

テーブル自体も、WORDではなく、(a*i >> 8)をBYTEにして持っておくと良いかも知れません。そうすれば、

     → (((Src1-Src2) * α) >> 8) + Src2
     → abyMulTable[Src1-Src2+256] + Src2

という、一回のテーブル参照で済みます。しかし、abyMulTableはマイナスも取り得るので、少しやらしいです。全体をDWORDで計算したほうがいいかも知れません。R,GBが各8bitであれば、最後の+Src2は隣のBYTEに桁あふれしないことは保証されるので、DWORDの足し算1回で済みます。具体的には、

     → (adwMulTable1[Src1R-Src2R+255] << 16) + (adwMulTable2[Src1G-Src2G+255] <<8) + (adwMulTable3[Src1B-Src2B+255] ) + Src2RGB

ですね。ここで、<<16,<<8のシフトが勿体無いので、このシフトも含めてテーブル化します。

     → adwMulTable1[Src1R-Src2R+255]  + adwMulTable2[Src1G-Src2G+255] + adwMulTable3[Src1B-Src2B+255]  + Src2RGB

255を足すあたりがやらしいと思うかも知れませんが、offset付きのアドレッシングを使えば、このテーブル参照自体は1命令で済みます。むしろSrc1R-Src2Rの部分のほうがやらしいです。あと、テーブルサイズが511×sizeof(DWORD)×3 = 6132バイト。うおー。L1キャッシュ4Kしかなかったら、溢れるやんけー。

そんなわけで、このアイデアは没。いいと思ったんだがな〜^^;

テーブルサイズで言えば、(x * α) >> 8を事前に行なったテーブルを作ったほうがいいかも知れません。BYTE abyTable[256]です。しかし、BYTEにすると、近接してBYTEアクセスすることになって、ストールするようなしないような..(どないやねん!) 結局、DWORDの掛け算テーブルで、

     → adwMulTable1[Src1R] - adwMulTable1[Src2R] + adwMulTable2[Src1G] - adwMulTable2[Src2G] + adwMulTable3[Src1B] - adwMulTable3[Src2B] + Src2RGB

とやりますか?これならば、256×sizeof(DWORD)×3 = 3072バイト。なんとか許容範囲ではないでしょうか。もちろん、前者は

(Src1 - Src2)×α >> 8

であるのに対し、後者は

(Src1×α >> 8) - (Src2×α >> 8)

なので、+1の誤差(かな?)は有り得ますが、そんなものは見てもわからないでしょう(笑)

しかし、αとβのテーブルを持つ手もありそうです。一見、テーブルサイズが2倍になりそうですが、この場合マイナスが出ないので、

     → adwMulTable1A[Src1R] + adwMulTable1B[Src2R] + awdMulTable2A[Src1G] + awdMulTable2B[Src2G] + abyMulTable3A[Src1B] + abyMulTable3B[Src2B]

とWORD、BYTEのテーブルで済ませることが出来るというアドバンテージがあります。テーブルサイズは、256*(sizeof(DWORD)+sizeof(WORD)+sizeof(BYTE))*2 = 3584バイト。最後の+Src2RGBが消えたことがどう出るか、というところで、前述のものとの損得は微妙ですが..。

とりあえず、今日考えたのは、そんだけ。いい方法を思いつかれた方は、やねうらおまでお気軽にどうぞ。


第C5回 timeSetEventは危険?(なのかなぁ..^^;) 00/09/06

関東の人は左利きだ。そう感じたのは、エスカレーターに乗ったときだった。関西では、利き腕で手すりを持って、右側で立ち止まる。急ぎの人のため左側を空ける。急ぎの人が掴む手すりは、利き腕ではなくなるが、それは急ぐのだからそれぐらいのリスクは背負えばいいだろうというのも根底にあるのかも知れない。いずれにせよ、立ち止まる人優先の考えかたである。関東では、立ち止まる人は左側である。これは逆に急ぐ人優先の考え方だろう。ここで、文化人類学的考察を行なう気は無いので、関東人はみんな左利きなんや!と安易に結論し、郷に入っては郷に従えの思想を踏襲し、やねうらおも左手で腕立て伏せをして筋力トレーニングに励む次第である。(そんなことはしなくていいという話もある^^;)


前回の続き。

     → adwMulTable1[Src1R] - adwMulTable1[Src2R] + adwMulTable2[Src1G] - adwMulTable2[Src2G] + adwMulTable3[Src1B] - adwMulTable3[Src2B] + Src2RGB

これですが、どうせSrc1Rを求めるのにビットシフトが必要なので右シフト回数を余分に1増やして、7ビット精度にすればテーブルサイズは半分で済むんでないかしらん?とか思ったわけです。まあ、見た目そんなにわかりゃしねぇーってことで。それならば、

     → adwMulTable1[Src1R-Src2R+255]  + adwMulTable2[Src1G-Src2G+255] + adwMulTable3[Src1B-Src2B+255]  + Src2RGB

こちらだって、ビット精度を下げれば

     → adwMulTable1[Src1R-Src2R+127]  + adwMulTable2[Src1G-Src2G+127] + adwMulTable3[Src1B-Src2B+127]  + Src2RGB

テーブルサイズは半分で済むので、L1キャッシュに入るような気が..。もう一つビット精度を落としてもクロスフェード系ならバレない!?


ところで、マルチメディアタイマでシステムに負荷を掛けると、DirectInputがキーを離した情報を取り逃がすようで困っています。(それ以降、そのキーは押しっぱなしのステータスが返ってくる+DirectInputがLOSTしているわけではない) 最初のうちは、そんなアホな〜と思っていたのですが、このサンプルを見る限り、やはり、そうなんだと思います。(コンパイルするにはyaneSDK2ndが必要) スペースキーを押しながら、リターンキーを何度か押してマルチメディアタイマを作るとスペースキーを離したあとも、スペースキーは押されているものとして情報が返ってきています。私のDirectInputの入力部分は、厳重にキーのLOSTとリストアを行なっているので、キーを取り逃がすことは無いですし、万が一LOSTしていたとしてもゼロクリアしているので、押されているという情報は返ってきません。

また、::GetAsyncKeyStateとかで判定すれば、こちらは正しく情報が返ってきます。ほとんどお手上げです。timeSetEventが使えないとなると、MIDIルーチンはどうなるんでしょうか?MCIを使うか、DirectMusicを使うかだと思うのですが、後者はDirectX3では動かないのでNT不可になってしまうので使いたくないのです。MCIだと、ボリュームフォードがオーディオミキサーでやるしかなくなるんで、外部のMIDI機器から直接再生している場合はボリュームコントロールできません。また、MCIでは再生して解析する時間が必要で(事前にopenしようにも、ファイル名を指定しないとオープンできない&2つのファイルを同時にオープンできない)大きなMIDIファイルだと再生までに数秒の遅延が発生します。

割り込みのインターバルを甘くするだとか、timeSetEventでTIME_PERIODICの代わりにTIME_ONESHOTを指定して、ワンショットタイマで、コールバック関数のなかで次のワンショットタイマをまた指定して..と擬似的にインターバルタイマを実現し、CPU負荷を下げることは出来るように思いますが、抜本的な解決にはなってないような..。

DirectMusicが使える環境ならば優先して使うようにすべきなんでしょうか...。キー入力ごときにDirectInputを使わないという手もありそうですが、それだと同時押しして離したときに、離したほうのキーが検知されることがあるので困ったもんです。


第C6回 マジカルアンチェリーク(謎^^;) 00/09/09

Keyの『Air』少しずつやってます(時間あまりとれないですが)。発売前の通販予約だとテレカ2枚(PureGirlの付録CDについてた画像データだが)、初回特典が音楽CD1枚余分に入ってます。メッセサンオーで予約して買うと先着2千名には等身大ポップ、そこからの予約分は時計がついてくるそうです。

まあ、プログラムはノベルにしてはきめ細かいなーと感じる半面、まあ、やって出来ないことはないかな、と。もちろん、技術的に出来ることと、それを実際にやることにおいては格段の隔たりがあるわけですが、どうやってんじゃーこれーと思わせたマジカルアンティークほどの衝撃は受けませんでした。

具体的に言うと『Air』ではキャラクターの表情等がトランジションで変わるとき、台詞ダイアログが一瞬消えるんです。多分、台詞ダイアログを残したままだと、速度が出せないためのような気がしますが、ちょっと拙いかなー(あるいは鬱陶しいかなー)と思いました。シナリオ面は、さすがによく練られています。(もう少ししたらゲーム批評のコーナーで書きます。さすがにまだやってない人がいるのにネタバレ的なことは書けないので^^;)

最近は、マジカルアンティークのように、立ち絵のアンチェリ(アンチエイリアス=エイリアシングの除去)のために、レイヤーマスクを持つのは半ば常識化しています。αチャンネルで256段階で持つか、もしくは50%のブレンド転送すべき部分だけのビットマスクを持ちます。前者は、普通にPhotoShopで描いてPNG等で書き出せばOKです。後者は、いろいろ方法はあると思いますが、PhotoShopならばブレンド転送すべき閉領域をパスで囲うとか、自動選択ツールで輪郭を選択して拡張するのがお手軽でしょうか..?

そんなこんなで、先週は、ゲーム制作のための理工学書風の本(詳しくは秘密^^;)を一冊書かないかと執筆依頼が来てました。うー。丸一冊ですか〜。

私には恐くて書けません(笑)

というのは冗談ですが、それにしても誤謬だらけになるのは恐いです。ただ、この手の本で私が参考になったと思えるような書籍は無く、実践的なゲーム制作が出来るレベルまで書かれているテキストは市販されていないのが現状です。ゲームのシーン管理は有限状態オートマトンとみなすだとか、ゲームオブジェクトはどう設計すれば良いかだとか、そういった基本的なことすら概説した本は、見受けられません。

私も業界経験は浅く、人に自慢できるほどの知識があるとは思えませんが、少なくとも現場レベルで、メインでアクションゲーム等を2ヶ月に1本制作+外注でその他にも仕事多数というハードスケジュールをこなしています。やはり、人より数倍速く仕事をこなせているのは、それなりのノウハウがあるからだと思っています。

そんなわけで、今後、何かの叩き台となれば、それでもいいんじゃないかとか思います。さすがに、本に書くならば実験は重ねますし、この連載ほど誤謬だらけにはならないでしょうけど(笑) それでも、一般書籍として出した日には、fjとかメーリングリストで叩かれるのは必至かなぁ〜^^; C++やオブジェクト指向方法論もεπιστημη先生ほど知ってればなぁ……(こういうときだけ先生とか書くやつ^^;) 本書くことになったら、もっかい入門書から勉強しなおすかー(笑)

まあ、ゲームプログラミングするには、あらゆる分野の能力が幅広く問われるわけで、特定分野に関してその専門家からお叱りを受けるのは仕方ないという意味もあります。清水亮氏(某氏の後輩らしい。要するに他人か..^^;)でさえ、神無月本(=『Direct3Dプログラミングガイドブック』/翔泳社)で、いくつかの誤謬を犯しているんだから、それを思うとやねうらおもちょっと気は楽かなとか(笑) いや、神無月本はいい本です。買えよ(笑) < と、さりげなくフォロー^^;

しかし、サンプルとしてソースをCDに収録するのに多少のバグが有ったり、急いで作っているのでソースが乱雑なのは仕方ないとしても、「なおしたにょ」とか「バグってたにゅ」とか書いてあるコメントだけはリライトしておかないと(笑) 完全に馬鹿だと思われる(もう思われてるって..)


第C7回  functorは函数合成の夢を見るか(何のこっちゃ^^;) 00/09/27

あのー、みなさん、コインランドリー行ってますか?

出し抜けに何を言うのかと思われるかも知れませんが、いまの流行はコインランドリーっすよ。(すんません。洗濯機買うお金も場所も無いんです^^;) 先日、コインランドリーに行ったんですよ。もちろん、昼間は仕事してますんで、夜中の4時とかです。

そこで洗剤買おうと、ごそごそと財布の小銭をfind_ifしていると(なんや?うちの財布はSTLのアルゴリズム対応なんか?)小銭が無いんですよ。自分でも、コインランドリー来るのに、小銭ぐらい用意して来い!って気もしますが、両替機ぐらい置いたれや!とそこは洗剤の販売機に視線だけで突っ込みを入れて、しばらく考えること10数秒。

その姿が、洗剤を買いたくて買えない貧乏学生か何かと思われたのかは知らないが(というか、まともな社会人は夜中の4時にコインランドリーに来ないだろう)、60ぐらいのおばさん(?)が近寄ってくるではないですか。

「なんや、洗剤なんか買わんかてええで!洗剤あるで!」

と言って差し出してくれたのが、20g入り洗剤の6個綴りのやつ。えっ。これ、いただけるんですかー。まじっすかー。ありがとさんです〜。

そう言って洗剤綴りを6個とりあえず受け取り、自分の洗濯ものの量を確認。えっと3リットルに1つだから、2つでいいかー。あとの4つは、返そうと思って、とりあえず隣の洗濯機において洗濯開始!すると、おばさんが凄い剣幕で近寄ってくるではないですか(笑) あたしゃ、4個はお返ししますよ、ちょっと待ってくださいよー。そう思って、見上げるとおばさんの手には、20g入り洗剤綴りがさらに6個あるではないですか。

「兄ちゃん、これも持ってき!」

な、なんや!まだくれんのかい!(笑) ちゅーか、わしは、そんなに貧乏に見えるんかい!などという突っ込みは無用。おばちゃんの好意を無にしてはいかんので、ありがたく頂戴する。洗濯を開始して、10分ぐらい本を読みながら待っていたところ、おばちゃんがまた近寄ってくるではないですか。今度はなんや?洗剤代でも請求されるんかい!?と思っていると、

「兄ちゃん、ランプついとる、このソフト剤入れ!」

渡してくれたのは、静電気を防止するためのソフト剤でした。知らんかった…。いまどきの洗濯機には、こんなん入れるんや。洗濯ゆうたら、川に持って行って、じゃばじゃばしながら足で踏んでたらええもんやとばっかり思ってたわ。(いつの時代やねん)

そのあと、おばちゃんは乾燥機に2枚しか無い洗濯ものを入れて自転車でどこかに消えていきました。私の洗濯が終わったころに戻ってきて、コンビニの袋から

「兄ちゃん、これ飲むか!」

と言って温かいコーヒーを出してくれました。さすがに悪い気がしたんですが、乗りかかった船なんで、もらえるものなら、なんだってもらうことにします(笑)

「あっちのコンビニは、駅前のコンビニよりスポーツ新聞来るの早いんやわ。スポーツ新聞、旦那が好きなんで」

とか世間話を聞いているとおばさんの乾燥機のタイマーが切れる。10分100円なので、2枚の洗濯ものならば20分も回せば乾燥すると思うのだが、おもむろに乾燥機の扉を開けると、首をかしげながら、さらに100円つぎ込む、おばちゃん。あれ〜。まだ回すんですかー?

「そういや最近、駅前のパチンコ、めっきり出やんようなったなー」

とか言ってくるではないですか。知らんっちゅーねんとか思いながらも、いろいろお世話になったんで、そうですかーとか適当に相槌を打つ。そうこうしているうちに、私のほうが乾燥まで終わってしまったので、最後にお礼だけ言って帰ってきたのであるが、これに後日談がある。

ある日、やねうらおが、そのコインランドリーに洗濯に行くと男二人連れが洗濯しに来ていた。

「そういや、最近、あのおばちゃんと会えへんなー」 「ああ、○○さん(実名)?会ったら、いつも話し掛けてきはるね。なんか、一昨年に旦那さん、亡くなられてから、えらい寂しいみたいで」

ありゃー。そうなんですか?あのスポーツ新聞は、ひょっとして仏壇行きだったんですか?それはなんとも気の毒な話ではないですか…。あのおばちゃんは話し相手を求めて、わずかばかりの洗濯物と、いくばくかの小銭、そして大量の20g綴りの洗剤を持って、毎日コインランドリーを練り歩いているでしょうか...!?

そんなことを考えると、たまらなく寂しくなるじゃないですか。洗剤ならいくらでももらってやるから、おばちゃんカムバーックである。(誰も洗剤もらってくれとは言ってないけどな…)


話が長くなりました。BCCで、yaneSDK2ndをコンパイルされた方が居たんですが、

it = m_alpInfo.erase(it);

というset::eraseの呼び出しの部分でエラーが出るんです。オンラインヘルプを引くと、こうなっています。

> void
> erase(iterator position);
> 反復子 position が指すマップ要素を削除します。
> 最後に削除された要素の次の要素を指す反復子を返します。
> 削除した結果,次の要素がない場合は,end() を返します。

返しますと書いてあるな?STLのset::eraseは、確かに削除した次の要素を指す反復子を返すのです。だもんで、この説明に間違いない。ところが、宣言はvoidです。なんじゃそりゃー!!voidでは値は返せません(笑)いや、そんなことはわかっちゃいるんだろうけど...とにかく、これは標準的なSTLと違うでないすか。もう知らん〜です(笑) しかし、setのヘッダーを見るとベースはHewlett-Packard製、それもVC++のと同じく1994年。母体は同じなんすか?そういや、古いSTLではeraseの返し値はvoidで、eraseした瞬間にiteratorは無効になるから、その直前にコピーしておきなさいというのをどこかで聞いたことがある気はするのですが…?うーむ。統一されていないインターフェースのSTL。STLのSは本当にStandardのSなんやろな?(笑) どうでもいいけど、ヘルプの内容だけ正しい仕様になってるっちゅーのは、どういうことなんやろか..(笑) いま流行りの超訳ってやつ?!^^;

ところで、STLで思い出しましたが、みなさんは関数オブジェクト(function object = functor)というのを知っていますか?

LISPは、関数自体がfirst class objectなので、関数閉包(function enclosure)と言って、関数自体の持ち歩きが出来て関数の合成とかが非常に簡単に出来たのですが、あれを擬似的にC++で行なうにはどうすればいいのでしょうか?関数ポインタを渡してコールバックは出来ても、それでは関数の合成は出来ないからです。

C++の世界でfunctor(=関数オブジェクト)と言うと、operator( ) を定義したクラスのことを言います。たとえば、find_ifのテンプレートを見ましょう。_Prは、functorです。

        // TEMPLATE FUNCTION find_if
template<class _II, class _Pr> inline
    _II find_if(_II _F, _II _L, _Pr _P)
    {for (; _F != _L; ++_F)
        if (_P(*_F))
            break;
    return (_F); }

_P(*_F)とあるのは、_Prはfunctorで、_Prにはoperator ( )が定義されているので、このオーバーロードされた関数が呼び出されます。見ての通り、_Prのとる引数は一つ(unary_function/単項関数)であることが、前提となります。引数を可変にするテンプレート展開が出来ないので仕方ないという意味もありますが、このへんはC++のテンプレート展開の甘さだと私は思います。

functorの合成について見てみましょう。

        // TEMPLATE STRUCT greater
template<class _Ty>
    struct greater : binary_function<_Ty, _Ty, bool> {
    bool operator()(const _Ty& _X, const _Ty& _Y) const
        {return (_X > _Y); }
    };

boolを返す二項関数(binary_function)としてgreaterは実装されてます。(structでもclassと同様に継承も出来れば関数も書けるというのは、意外と知らない人が多いかも知れません) こいつと、bind2ndで、関数合成をやってみましょう。

        // TEMPLATE FUNCTION bind2nd
template<class _Bfn, class _Ty> inline
    binder2nd<_Bfn> bind2nd(const _Bfn& _X, const _Ty& _Y)
        {return (binder2nd<_Bfn>(_X,
            _Bfn::second_argument_type(_Y))); }

bind2ndは、binder2ndというfunctorを返す関数テンプレートです。2項関数の第2引数に、値を突っ込んで、単項関数化するわけです。

    remove_if(lst.begin( ),lst.end( ),bind2nd(greater<int>( ) , 5);

STLのremove_ifと組み合わせて、上例のように書けば、5より大きな要素をもつものを全て削除できます。greater<int>のあとに( )があるのは、C++に馴染みの少ない人には見慣れない構文でしょうが、これ

    CObject( ).func( ); // CObjectのテンポラリオブジェクトを作り、そのメンバ関数funcを呼び出す。

と同様、functorのテンポラリオブジェクトを作るためにコンストラクタを呼び出しているのです。ここで面白いのは、このように行なったfunctorの合成は、インライン展開される(たぶんね^^;)ということです。おかげで、効率が非常に良いです。

問題として挙げられる点は、テンプレート展開に頼るので、静的な(コンパイル時の)functor合成しかできないこと、そして、関数が単項関数か二項関数か、それ以外かを最終的に処理する関数(上例ではremove_if)が決め付けてしまうことが難点です。そもそも、remove_ifがfunctorに規定すべきことはboolを返す関数であるということだけで、functorの引数に関しては口出ししてくるのはおかしいです。上例では、greate<int>というfunctorは二項関数であり、それをbind2ndで単項関数化しています。この仕組みがダサイこと極まりないのです。テンプレートで文字列をソースコード中に展開する機能、たとえば

    remove_if(lst.begin( ),lst.end( ),element:"element > 5");

というようなことが出来れば、bind2ndやgreaterのような似非functorの合成作業なんて不要になります。どうせ静的にしかfunctorの合成が出来ないのならば、これぐらいでも十分です。

私がSTLのalgorithmがクソだと思うのは、このへんの事情からで、functorはともかく、algorithmはほとんど使いものにならないです。しかも、functorもよほど限定されたシーンでないと使いものになりません。私に言わせれば、テンプレートの展開が甘いがために無理矢理、別の機構で代替しているだけで、本来の数学的なfunctorの意味合いからはほど遠いです。

C++のベテランと呼ばれるような人たちは、どうもこのへんの機能を駆使してプログラムすることが言語機能を十二分に活かした設計であり、技術であるように錯覚している節がありますが、言語機能に極度に依存する上に、しかもたいした汎用性も得られないのでは、勉強するほどの価値があるとは到底思えません。言うまでもなく、もっと強力なテンプレート展開を行なうプリプロセッサを自作すれば済む話です。そのようにしてメタレベルのプログラミングとまともに付き合おうという人が少ないのが、残念で仕方ありません。

もっとはっきり言いましょう。重箱の隅をつつきあうような閉塞的なC++コミュニティはもうたくさんです。初心者の質問(私も含む)に対して「あなた、そんなことも知らないんですかー?入門書読みましたかー?C++の仕様では基底クラスのコンストラクタは派生クラスの関数は呼び出せないようになってます(呼び出せたら危険でしょ)」という頭の悪い答えしか返ってこない。違うでしょーに。コンストラクタから派生クラスの関数を呼び出せると便利です。そこまでは真実だし、事実そうしたい機会は多いのです。だから、C++に、すべての派生元のコンストラクトが完了してから2段階目に呼び出されるコンストラクタを書ける構文があれば良いんです。ところが、残念ながら、そういう構文は用意されていない。だから、もし本当に必要と思うならば自分で作ってみましょうよ、と。そういう発想や返答が正しいんじゃないんですか?

少なくとも中級、あるいは上級に属するプログラマで、yaccやlexを駆使できるくせに、C++を変更不可能なツールのように扱うのは、よしましょうよ。C++の言語仕様を無前提に受け入れるのを、やめましょうよ。そして、こうなっていたほうが良いと思う部分については、実際に自分で言語仕様を拡張してみましょうよ。参照体が悪だと思うなら、参照体を使わなければいい。でも、参照体を使わなくなった結果、cin >> i ; のような演算子オーバーロードを解決できないから困るだろ?とベテランプログラマに突っ込まれたなら、「自分の言語C**には、それ専用の構文を用意しますから、参照体のような馬鹿なギミックはいりません」だとか「あなた、cin >> i;なんて直感的にわかりにくい表現がいいと思ってるんですか?センス無いですねー!僕の言語C##ではこうやります」と、胸を張って言えばいい。(あとのことは、やねうらおは一切関知しませんのであしからず(笑)) そういうスタンスで取り組んだときに初めて、C++の創始者Bjarne Stroustrupと同じ場所に立てるんでないかと思うのですが、いかがなもんでしょうか。


第C8回  slicingと冷害処理(なんや?今年の冬は寒いんか?) 00/10/01

うちのおかーちゃんからの電話で起こされました。

「あんた〜、大変や!」

おかーちゃん、どうしたんや?

「バスが来たわ…お迎えや」

何のやねん!お迎えは、霊柩車とちゃうんか!?

「パソコン触ってたらバスが来たわ!」

えっ??それは、どういうこっちゃねん!

「それも、ウイルスのバスやわ!」

んー?ウイルスのバス、ウイルスバス、ウイルスバスター!おかーちゃん、それタイムスケジュールしてあって、金曜の5時なったら勝手に起動するやっちゃがな!

「あんた、ウイルス作ってんのか?」

作ってへんわ。人聞き悪いこと言うなや。

「しかも、バス!!」

え?ウイルスのバス!?それどういうことー?

「警察捕まらんようにしいや!」

余計なお世話じゃい!もうつまらんことで電話掛けてくんなよ〜(笑)


まあ、そんなこんなで、今回は、slicingの問題なのです。(いや、上の話とは全然関係ないけどね^^;)

C++でslicingと言うと派生クラスの変数(オブジェクト)を基底クラスの変数に代入したときに、単にメモリコピーが成されて、派生クラスのメンバ変数とかが削ぎ落とされることを言うのですが、では、このスライシングを起こした変数に対してvirtualな関数を呼び出すと、果たして呼び出されるのは派生クラスの関数でしょうか?

…察しのいい方ならばもうお分かりになられたかも知れません。

派生クラスの関数が呼び出されると、そこで派生クラスのメンバにアクセスしたら大変だと思った神様が、勝手にベースクラスの関数を呼び出すようにvtableへのポインタの摩り替えをやってくれるのです。ちゅーか、そんなん余計なお世話じゃい!お前は下僕のようにメモリコピーだけしとったらええねん!、という気もします。ベテランプログラマからは例によって「派生クラスのメンバにアクセスしたら即アウトじゃん。やねうらおって馬鹿っでねーの?」って声が聞こえてきそうですが、そりゃ派生クラスのメンバ変数にアクセスしたら落ちますが、派生クラス側のメンバにアクセスしないことを保証する関数を宣言できる構文を用意して、その関数ぐらい呼び出せてもええんでないですの?

たとえばRTTIを使うと極端にコストが掛かるから、virtual int GetType( ) { return 10; } なんてやって、自分のtype IDを返す関数を書いているかも知れない。これは、メンバをアクセスしないからセーフです。こういう関数を呼び出して、自分の動的な型を調べたいってことは、しょっちゅうあるのです。

まあ、ともかくスライシングが発生した時点で、vtableが書き換えられるということは、正しくデストラクタも呼び出されないし('00/10/05修正/いや、私の勘違いです。派生クラスのデストラクタが呼び出されないのは問題ではないですね… →詳しくは第C9回にて)、お前はもう死んでいる状態なのです。(古すぎ) つまりは、スライシングが発生するコピー自体をエラーにすべきです。なぜ出来てしまうのか、やねうらおには不思議でなりません。

ともかく、スライシングの解決法は、2つあります。一つはポインタのコピーに頼る方法、もう一つは参照を使う方法です。参照も、コンパイラがdeceptive(欺瞞的)にしているだけで、実体はポインタですから、スライシングの問題は発生しないわけです。さらにこの考え方を推し進めていけば、コピーによってスライシングの発生しない言語実装の方法がわかると思います。実際、そのような言語はいくつかあります。表向きはどうであれ、以上の理由から内部的には、すべてポインタで管理しなくてはなりません。コピーするときには、以前のオブジェクトを解体し、新たにオブジェクトを生成し、コピーし、ポインタはそこを指すようにします。この方法はスライシングが発生しないという点で洗練されてはいますが、オブジェクトの解体〜生成(C++で言うならdelete〜new)は昔のマシンにとって結構負担だったことを考えると、C++の採ったアイデアは現実的な選択だったと言えるでしょう。

さて。例外を投げて受けるときに受け渡し方法は3つあります。値渡し、ポインタ渡し、参照渡しです。値渡しは、上記のスライシングの問題が起きるので、使えません。ポインタ渡しは、

void func(){
 exception ex;
 throw &ex;
}

ってなコードをうっかり書いてしまうと、このポインタ&exは、スコープアウトした時点でその実体exが無効になるので、事実上使えません。残るは、参照で、参照で受ければコピーされたテンポラリオブジェクトを指す参照が得られ、スライシングの問題は発生しません。つまり、例外は必ず参照で受けなければならないと言うことです。

例外にまつわることは、これぐらいでは終わらないのですが…。きっちりと例外処理に対するコードを書こうと思うと、それなりのノウハウが必要になります。例外処理機構はソフトウェアにとって必要なものですが、C++にとってはちょっと言語スペックを超えているような気がします。例えて言うなれば、自転車で高速道路を走るようなものです。そこまでやらんかてええやん、みたいな(笑)

たとえば、コンストラクタのイニシャライズのときに発生した例外

  CObject::CObject() : m(0) { // m(0)で発生した例外を受けるために、その前後にtry〜catchを書けなければならない。

を捕捉する構文も、C++なんちゃら委員会で策定されたはずなのですが、まだVC++6.0ではサポートされていません。ついでに言わせてもらえれば、まだC++の例外処理メカニズムはまともに使えるレベルまで洗練されているとは到底思えません。コンストラクタで発生した例外に対応するためには、ポインタはauto_ptrでなければならないし(コンストラクタの途中で例外が発生した場合、そのデストラクタが呼び出されないため、資源が漏れる)、かと言って、それの配列バージョンであるauto_arrayは用意されていない。(やねうらおは自分で作りました)

何考えてんのかーと言いたくなります。要するに、コンストラクタでは例外の発生するようなコードを書いてはいけないということですか?ということは、例外の発生しそうなコードがある場合は、2段階目に分けて初期化しなさいということですか?引数が多くて2段階で初期化するのはわからなくもないですが、例外対策のためだけに、引数無しの初期化関数を呼ぶんですか?なんちゅーダサさ。なんちゅー頭の悪さなんでしょうか。要するに、前回書いたように2段階目に呼び出されるコンストラクタを書ける構文を自分で用意する、というところに行き着くのです。

かく言う私は、2段階初期化がめっちゃ嫌いです。言うまでもなく2段階初期化をやるのが面倒だからです。そりゃ一つならいいですよ、一つなら。それの配列だったとき、ループを回さなきゃならない。ループを回すのに、変数をいちいち宣言しないといけない。あんた馬鹿ですかー?どうして全ての配列要素に対してループする構文がないんですか?それにループ回数だけしか問題にならないことだってあるし、そのときもいちいちループ変数を宣言しなきゃなんないんですか?数式解析ツールのmathematicaでさえより洗練されています。やねうらおは、先日、mathematica4を買いました。殺人的マニュアルのでかさ、たぶん、これで人殴ったら脳震盪起こして死にます(笑) いや、人殺す話はどうでもいいのですが(笑)、mathematicaの構文では、こうなっています。たとえば、ループしてテーブルを作成するTable命令では、

Table[f,{imax}] // fをimax回計算し、値をリストとして構成する
Table[f,{i,imax}] // iを1刻みで1〜imaxに変えfを計算し、値を1次元リストとして構成する
Table[f,{i,imin,imax}] // iを1刻みでimin〜imaxに変えfを計算し、値を1次元リストとして構成する
Table[f,{i,imin,imax,di}] // iをdi刻みでimin〜imaxに変えfを計算し、値を1次元リストとして構成する
Table[f,{i,imin,imax} , {j,jmin,jmax} , ...] // fを計算し、値を多次元リストとして構成する

そうです。値が1つならばそれはループ回数だし、2つなら、ループ変数とループ回数。3つならばループ変数と最小・最大。4つなら、ループ変数と最小・最大・ステップ。それらを並列表記すれば、多重ループ。何とわかりやすいんでしょうか。mathematicaを触ったあと、C++を触り、これをいちいち

  for(int i=0;i<10;++i){

なんてタイプするだけでも反吐が出そうです。もうloop (10) {  でいいじゃん〜って気がします。ついでに言えば、VC++はまだ少し古いC++に基づいているのでこのiの変数スコープはforの外まであります。下のほうで、さらにforでiを変数にしてループしてるとそこでまたエラーが(笑) 殺すぞ〜おら〜!!ループはコピペ(コピー&ペースト)で貼り付けたりすることが多いのに、これは困るやろ? さ〜さんが遊びに来たときに、これ、どう対処すればいいんでしょうか?と言われたんですが、そりゃ

 { // 箱入り娘^^;
  for(int i=0;i<10;++i){
   ・・・
  }
 }

こうでしょうと言って、失笑を買いました(笑) でも、これならコピペでも大丈夫なんですよ。あまりのダサさに失笑ものですが(笑)

まあ、このようにさらっとloopを書く構文を、defineかtemplateで解消できれば良いのですが、C++のdefineでは引数の数や型を判別してマッチングすることは出来ないし、templateはクラスの型汎用性ばかりで、こういう展開には何の力も発揮しない。せめて、defineより強力なマクロ展開を行なうプリプロセッサの機能が欲しいところです。C++の使いもしない馬鹿でっかいだけのライブラリはいらんから、そういうところに力を注いだらんかい!とか思います。もううんざりです。

前回も言いましたが、それはC++が関数合成を苦手とすることに起因します。たとえば、私ならば、以下のような記述が出来るように言語セットを拡張するかも知れません。

  functor statement loop(const var n) statement s1{
    for (int i=0;i<n;i++)
      s1; // これが束縛されたstatement
  }

これで、loop(10) { ... }という表記が使えるようになります。(なると、いいね(笑)) このfunctorの返し値(というか、構文解析のときに返すべき非終端記号)としてはstatementになります。BNF記法で書けば

  statement := loop statement

の意味です。まあ、そのへんはどうでもいいのですが(笑)、

  functor statement loop() statement s1 { while(true) s1; } // これならば永久ループ

だとか、

  functor statement loop(var& i,const var n) statement s1{
    for (int i=0;i<n;i++)
      s1;
  }

だとか。後者のほうは、これで、loop(i , 10) { ... }という表記が使えるようになります。要するに、このようにstatementを持ち歩くことさえ出来れば良いのです。(本当は静的にではなく動的に持ち歩けたほうが良いのでしょうが..)

まあ、そんなことを考えて、うんざりしながら、配列で確保したオブジェクト、本来ならば、そこで終わっているものを、何の因果かは知らないけど2段階目の初期化を呼び出さなくてはならない。そのためだけにfor文でループでまわして、このとき、要りもしない変数を宣言して、2段階目の初期化関数を呼び出して...

  おんどれ、なめとんのかー!!おりゃーー!!

突如としてそう叫びたくなる、やねうらおの気持ち、わかっていただけるでしょうか(笑)

そんなわけで、この2段階初期化を実装する話は、次回に持ち越し。例外処理の話は、時間をかけてゆっくりやります^^;


戻る