第C9回 ポインタを捨てて、街へ出よう!(集約関係オブジェクト的近未来) 00/10/06
いま、某社の麻雀ゲームの企画文書書いてます。忙しいのか忙しくないのかよくわからない日々を送りつづけてます。なぜに、麻雀の企画文章を私が書かないといけないのかについては定かではありませんが(笑)、この麻雀、プレイヤーから打ち筋を学習するんです。やねうらおが大学のときに独自に研究していた「例からの帰納的学習」ってやつです。しかし、この企画書に添ってプログラムするのは下手すると、自分自身なので、やっぱ、そんな企画やめとこか〜みたいな(笑)
そもそも、打ち筋だけをパラメータとして学習したところで、強くなりませんて…。概念そのものを見つけ出し、見つけた概念を合成し、あらたな概念を作り出すようなタイプでないと。(その昔、A.M.Lenartの論文を参考にしてLISPでそういう学習プログラムを書いたことはある) そこまでやったとしても、その学習したという成果がプレイヤーに見えないことには、教えたということに対して何の快楽も得られないのではないでしょうか..。要するに、打ち筋を学習するだなんて、企画レベルで失敗してると思うんです。コンピュータの成長が見てとれなくてはいけないんです。なんや?それなら、コンピュータのレベルに応じて、配牌をインチキしたら簡単やんって言われそうですね。うっ…そうかも(笑)
そういや、yaneSDK2ndの回転のサンプル(サンプル8)は見ていただけたでしょうか。いつもより多めにまわしております^^; 結構な速度が出るもんで驚いています。こんなメールももらいました。
どうも。yaneSDK2ndに激ハマリ中の、ひさです。 もう50000回転位してますが、未だにハマリ中です。(意味不明) ところで。 サンプル8で、画像が逆さまになったときに スカートの中身が見えませんが、これはバグでしょうか?) |
ええ、それはバグです(笑) 修正はしません(笑)
さて、前回の続き..前回、私は、いくつか勘違いをしていて、鈴希さんから指摘をうけたのでいくつか補足します。
まず、派生クラスを基底クラスへ代入したときには、vtableが摩り替わるのではなく最初からあったvtableが破壊されずに残るだけ。そりゃそうですね。代入を単純なビットコピーと考えて良いのはC言語の範疇だけですね。失礼しました。
また、派生クラスを基底クラスへ代入したあと派生クラス側のデストラクタが呼び出されなくて困るのか?と言われたのですが、よく考えてみれば困りませんね。代入後、派生クラス側の関数を呼び出したいことはあるかも知れませんが、スライシングが起こった時点で派生クラス側の関数の呼び出しは不正。前回私が言ったように、派生クラス側で、派生クラス側のメンバにはアクセスしていないことを保証するための構文を導入して、その関数ならばスライシング後も呼び出せるようにすればどうかというのは悪くないにしても、おそらく常識的な実装ならばvtableは1本増える。スライシング後にtypeinfoを得るためだけにvtableが1個増えるならば、最初から基底クラスにメンバとしてtypeinfoを保持するためのメンバを持たせればいいわけで―――というか、派生クラスを基底クラスに代入したあとに、そのtypeinfoって何なんやーみたいな(笑)
きっと、代入のセマンティクスをどう捉えるか、なんだと思いますが、C++は実体を直接扱う(扱える)わけで、このような実体を直接扱う実装系ならば、実体を使った派生クラス→基底クラスへの代入によって、オブジェクトの形が変わるのは当然で、逆にこれが変わらないのならば、実体を直接扱っていることにはならないし、C++の設計理念と反するのではないか、ということですね。もちろん、その通りです。
代入のセマンティクスを追求していくと、とっても興味深い問題になるとは思うのですが、結局のところ、プログラミングとは妥協の産物なので、smalltalkみたいな代入が正しいとか正しくないとか良いとか悪いとか言う問題ではなく、そのプログラミング言語が対象とする領域でどの程度の現実性を持っているかに行き着くのでしょう。そういう意味ではC++は当時としてとても現実的な選択をしていたということですね。
さて。コンストラクタで、例外が発生すると、デストラクタは呼び出されません。逆にコンストラクトが完了していないのに、デストラクタが呼び出されると困ります。たとえば、
A::A() { m_b = new B; m_c = new C; }
A::~A() { delete m_b; delete m_c; }
のようなコードがあって、m_bのnew Bでメモリが足りずにbad_alloc例外が発生すると、m_cの値は不定のままデストラクタが呼び出されることになって、delete m_cの部分で、不正なメモリをdeleteしてしまいます。このような理由により、コンストラクタで例外が発生した場合は、デストラクタは呼び出されません。
よって、コンストラクタでnewしたオブジェクトをデストラクタでdeleteしているから良いということは決してなく、newしたときにbad_alloc例外が発生したときにそなえて、ポインタはすべてauto_ptrでなくてはならない。ふむふむ。auto_ptrが、作られた理由はなんとなくわかってきたぞ。まー、auto_ptrを考えた奴は、それなりに賢かったわけですよ(笑)
それならば、配列のときはどうすればいいのか?auto_ptrの配列版は?それが用意されてないわけです。なんじゃそりゃー!!auto_ptrを考えた奴は、やっぱり馬鹿やったわけですよ(どないやねん..) そんなわけで、auto_arrayテンプレートを、用意したので興味のある人は、yaneSDK2ndのYTL/auto_array.hのコードを参考にしてください。
まあ、何故auto_arrayが用意されていないのかは想像だに難くはありません。前回のスライシングの問題です。スライシングの発生する言語処理系では、ポリモーフィックな配列は不可能なのです。具体的に言うと、C++の配列演算はポインタ演算に還元されます。たとえば、A a[10];という配列に対してa[5] == *(A*)((BYTE*)&a[0] + sizeof(A)*5)なわけです。ここでsizeof(A)というのが出てきますが、ポリモーフィックな配列を使えないのは、これが原因です。
ところで、集約関係にあるオブジェクトを、わざわざポインタで管理するのは、
1.遅延生成(lazy construction)
2.ポリモーフィズムのため
の2つでしょう。そもそも集約しているオブジェクト(部分オブジェクト)とライフタイムが同じならば、ポインタなんて使う必要はありません。これ
class A{ B m_b; // BはAの部分オブジェクト。ライフタイムが同じなのでポインタではなく実インスタンスを持てば十分! }; |
で十分です。
つまり、ポインタで管理するのは、1.のようにオブジェクトのライフタイムが異なり、遅れて生成されるという場合と、2.のポリモーフィズムな型を与えられる場合の2つしか考えられません。(あとC++では配列サイズを動的に決定する構文を許していないので、そのためだけにポインタを使うことはありますが..)
ということで、ポリモーフィズムの使えないような配列ポインタは、魅力は半減します。ポリモーフィックな配列を使うために、vector< auto_ptr < A >>で代用する手はあります。要素ひとつ一つに対してnewするため、メモリー効率は悪いです。(しかし、C++でポリモーフィックな配列を実現するには、これが現実的な解決策ですが)
次に2段階初期化構文について考えてみます。これを無くすためには、どうしたら良いのでしょうか?何度も言うように、2段階初期化構文は必要悪という言い方は出来るかも知れませんが、私には必要だとは到底思えません。
たとえば、CPU判別をして、そのCPUに応じて、そのCPU用の派生クラスをnewして返すルーチンを考えてみましょう。
CPlaneBase* DrawFactory( ) { switch (GetCPUID( ) ){ case 0 : return new CDrawP5; case 1 : return new CDrawMMX; case 2 : return new CDrawP6; } } |
これは、言うまでもなくデザインパターンで言えばFactory Methodの一種です。ところで、このCPlaneBaseが配列になるとどうなるんでしょうか?その初期化は、こんな風になりますか?
CPlaneBase* lpPlane[10]; for(int i=0;i<10;++i){ lpPlane[i] = DrawFactory(); } |
ダサいこと極まりないです。もちろん、この解放作業も必要になります。解放作業は、vector<auto_ptr<CPlaneBase> >を使えば少しは楽になりますが、それでも生成がこれだけ鬱陶しいと、嫌になります。ポインタで管理しないといけない、ということ自体が管理コストの増大を意味します。ポインタでの管理は、必要悪ではありません。
どうすれば良いか?解決策は、こうです。
CPlane::CPlane(){ m_lpPlane = DrawFactory( ); } |
コンストラクタでFactory Methodを呼び出し、auto_ptrでそれを管理します。CPlaneの関数要求は、すべてm_lpPlaneのほうに委譲します。私は以前、これをPrototypeパターンと呼び、「あれはFactory Methodではないのか?」と某所で突っ込まれましたが、違います。Factory Methodは、動的な生成に主眼を置くものであって、ここでは生成そのものが問題ではないのです。また、自分の集約しているオブジェクトを(内部的に)初期化し、外部からのメッセージはすべてその集約オブジェクトに委譲すると言う意味でPrototypeパターンだと言ったのですが、作成動機がPrototypeパターンとは少し異なります。
ここまで読んでくださった方にはいまさら言うまでもなく、私の目的はポインタによる管理から逃れることです。言い換えれば、面倒な初期化作業、解放作業から逃れることです。そういう意味では、これは典型的なデザインパターンのどれにも分類されないのかも知れません。そんなわけで、ここでは仮に代理母パターンと呼ぶことにします。
ポインタでの管理が何の苦痛でも無いという人がいますが、そんな人の書くプログラムは、私は信用できません。ポインタで管理すること自体が管理コストの増大、すなわち、バグの発生率を上げます。まして、初期化作業が必要ならば、初期化忘れが発生します。
本題に戻って、2段階初期化から逃れる方法について説明しましょう。もうわかったかも知れませんが、この代理母パターンのあと、2段階目の初期化関数を呼びます。
CPlane::CPlane(){ m_lpPlane = DrawFactory( ); m_lpPlane -> Init(); } |
これで、2段階初期化からは逃れられます。ただし、このような代理母クラスが必要になります。呼び出された関数をすべて委譲するためのコードが必要になります。実行時のオーバーヘッドは無視できるオーダーでしょうが、このクラスを使うほうは楽でも作るほうは、いちいち保守するのが非常に面倒です。どうすれば良いでしょうか?テンプレートで逃げられないでしょうか?
たぶん、逃げられません(笑) C++の関数テンプレートの弱さですね^^; たとえば、こんなテンプレートを作って、
template<class T> class surrogate_mother { public: surrogate_mother(){ m_ptr = new T; m_prt -> Init(); // 2段階目初期化 } ~surrogate_mother(){ delete m_ptr; } T* operator->() const {return m_ptr; } protected: T* m_ptr; // こいつに委譲するのだ }; |
typedef surrogate_mother<CPlaneBase> CPlane;
とかやって、
CPlane plane; plane->Load("xxx.jpg"); |
とやって使う手はあります。ポインタでも無いのにメンバを呼び出すのが -> というところらへんが非常に気持ち悪い気がします。せめてメンバ参照の . (ドット)をオーバーロードさせてくれ〜って気はします。
結局のところ、例外処理は2段階初期化の問題と摩り替わり、代理母パターンと合流して、テンプレート展開の脆弱さに帰結したのでした。こうやればいいよ!ってアドバイスがあればよろしくお願いします。
継承を言語機能としてサポートしているくせに、クラスのコンポジションの言語的バックアップ(例:上で出てきた委譲するだけのコードを延々と書かなければならないなど)が非常に弱いのがC++の難点です。そのへんをビジュアルに行なえる開発環境があれば良いんでしょうけれど...。
第CA回 auto_ptr間のアップキャスト(なんで出来へんの〜) 00/10/12
またもや、うちのおかーちゃんからの電話で起こされました。
「あんた〜、大変や!」
おかーちゃん、どうしたんや?
「あいめが、愛芽がおらんようなった…」
えっ。それは大変や!!えらいこっちゃ!!どうすんねん!って言うか愛芽ってなんや?あんたの隠し子?ということは、俺の生き別れの妹?
「何馬鹿ゆうてんの!愛芽ゆうたら、あい・めーでしょ!」
ああ、なるほど。あい・めーですか?すみません。残念ながら、当方、中国語は理解できません。
「中国語とちごてmath愛芽!」
えっ?何?math愛芽?数学的隠し子ですか?(笑)
「何ゆうてんの!あんたも感じは、math愛芽でしょ!」
俺も、感じはmath愛芽なんか?俺も、math愛芽って感じ〜?ちゅーか、それ何の話なの?
「そりゃウイドウズのんに決まってるやん…」
ウイドウズって何よ?未亡人達?ちゅーか、Windowsですか?(笑) ということは、感じのmath愛芽って、ひょっとして、漢字入力するMS-IMEのことですか?MS-IMEがmath愛芽…。お笑いか、それ…。
んなもん、Alt+漢字で出てくるんとちゃうんか。どうも話を聞いていると、Alt + ESCを押していたらしい。も〜。朝から阿呆みたいな電話してくんなっちゅーに!
「愛芽はやめて、これからは漢ちゃんって呼ぶようにするわ」
愛芽でいいよ、愛芽で。(笑)
さてさて。今回は、auto_ptrによるアップキャストについて簡単に説明。
auto_ptrが、そんなに利口で、通常のポインタと同じように扱えるならば、継承関係のあるauto_ptr間でアップキャスト出来てもよさそうなもんである。つまり、class Aからclass A_derivedが派生されているとしたら、
auto_ptr<A> pA; pA = pA_derived; // これは可能か? |
ということ。
実際には、AとA_derivedには継承関係があっても、auto_ptr<A>とauto_ptr<A_derived>には継承関係が無いので、これは、失敗します。コンパイラがメンバ関数テンプレートをサポートしていれば、うまくいくのですが、VC++6.0でもメンバ関数テンプレートをサポートしていないので、この変換は出来ません。
どうするかというと、
pA = &*pA_derived; // これは可能 |
こんなんでええんか? 所有権の移動まで兼ねて行なうならば、
pA = pA_derived.release(); // これも可能 |
こうですか? もちろん、私が作ったauto_arrayのほうは、ポリフォーイズムをサポートしていないので、どちらも不可です。auto_arrayに、要素のひとつのサイズをメンバとして持たせて、operator [ ] をオーバーロードして、
T* operator [ ] (int n) { return T*((BYTE*) ptr + nSize); |
で、配列要素にはうまくアクセスできるので、ポリフォーイックなauto_arrayは可能なのではないでしょうか?
このアイデア、部分的には上手くいくのですが、配列のデストラクタがうまく呼び出されません。デストラクタを明示的に呼び出そうにも、確保されている配列サイズがわからないので、それも不可。あちゃちゃ〜。確保までauto_arrayの構文で明示的に行なおうと思っても、コンストラクタで引数を取る場合は、テンプレートで書けない。つまり、任意の数の引数とマッチして、それをテンプレート展開してくれる構文と、配列のnewで引数付きで行なえる構文が必要なわけですが。イメージとしては、こんな漢字、もとい感じ。
template class <T> class auto_array
{ |
ともかく、そんなわけで、vector<auto_ptr<T> >を使うことになるのです。これのテンプレートは、yaneSDK2ndのYTL/auto_vector_ptr.hがそれです。よかったら参考にしてください。余談ですが、最後の> >は、>>とやると右シフト演算子だとみなされるのでスペースが必要なのです。最大トークン法でparsingするんで、こういうことになるんですね。何か異様にダサい気がするのは私だけでしょうか??
第CB回 PhotoShopのレイヤ合成アルゴリズムについて(YGAフォーマットを提唱する) 00/10/18
ついにやりました!!アンチェリ転送に成功しました!
yaneSDK2ndのサンプル12で公開しようと思っているのですが、アンチェリ転送に成功しました。ここで、簡単に原理を説明します。
まず、画像からαチャンネル(PhotoShopで言えば「不透明度」)情報を抽出しなくてはなりません。PhotoShopで言えば、PNGの書き出しでそれを行なえますが、PhotoShopは非圧縮PNG書き出しが出来ないので、これを扱うのは非常に不便なのです。
非圧縮PNGについては、さ〜さんのページに資料的価値の高い解説文が存在します。しかし、たかがαチャンネルを抜き出すためだけに、わざわざPNGの読み込みルーチンを作るのは苦痛です。仮にそれがlibpngで簡単に作れるとしても、今度はPNG書き出しが出来ないツールだと大変です。PNGを使わず、そして、もっと簡単になる方法は無いでしょうか?これが今回の課題でした。
そこで、考えたのは、PhotoShopのレイヤ合成のアルゴリズムについてです。私が見る限り、PhotoShopのレイヤ合成は、エネルギー分配に基づくもので、一番手前のレイヤにまず100%のエネルギーが与えられます。一番手前のレイヤの不透明度が仮に60%だとすれば、そのレイヤに60%のエネルギーが割り振られ、残りの40%のレイヤが後段のレイヤに行きます。後段の(2番目に手前の)レイヤが仮に75%の不透明度であれば、この40%のエネルギーの75%が、2番目に手前のレイヤに割り振られます。つまり、40%×75%=30%ぶんです。さらに後段のレイヤでは、残った10%のエネルギーが割り振られます。これを、エネルギーが0になるまでこの処理が繰り返され、割り振られたエネルギーに基づいてピクセルの値(RGB)を重み付きで平均を行なうのです。
しかし、単純に2枚のレイヤしか無いときは、
p α + q (1−α) |
です。pは手前のレイヤのピクセル値。qは後ろのレイヤのピクセル値。αは手前のレイヤの不透明度(0〜100%) いま、αは0〜255の間の数字で欲しいので、この式は、
p α/255 + q (1−α/255) |
と変形できます。(αは0〜255ね)
そして、後ろの(透明な)レイヤを、白に塗った画像Aと、黒に塗った画像Bの2枚を用意します。このとき、qの(R,G,B)=(255,255,255),(0,0,0)ですね。つまり、上の式に代入すると、
Aの画像:p α + (255,255,255)(1−α/255) |
です。Aの画像からBの画像の差を取ると、(255−α,255−α,255−α)となり、RでもBでもGのいずれも255−αとなるので、255からRの値でも引いてやれば求めるべきαの値となります。
サクっとプログラムを書きましょう。
for(int y=0;y<sy;y++){ for(int x=0;x<sx;x++){ DWORD dw; dw = *p[0] & 0xffffff; // 最上位バイトをマスク BYTE alpha; alpha = 255 - ((*p[1]&0xff) - (*p[0]&0xff)); dw += ((DWORD)alpha) << 24; // 最上位バイトに持ってくる *(pd++) = dw; p[0]++; p[1]++; } } |
実に簡単です。あとはこのαチャンネル付きのデータにヘッダをつけて、実際のボディはyaneSDK2ndのCLZSSで圧縮すればαチャンネル付きの画像フォーマットが完成。Yaneurao Graphic format with Alpha channel略してYGAと命名。yaneSDK2ndのCDIB32では標準でこの画像を読めこめるようにしてあります。このあと、αチャンネル有効な転送処理をいくつか追加。
ただし、このα値の有効なブレンド転送は、0−255の間なので、さきほどの
p α/255 + q (1−α/255) |
で行なわなければならないのですが、この255で割る作業が、結構、ハイコストなのでビットシフトで済むように通常、256で割ります。そうすると、α=255にもかかわらず色が少し減衰するという現象が発生します。これを防ぐために、αが255のときは256に補整する処理を入れます。これでバッチリです。ちなみにヌキ色有効な転送は必要ありません。ヌキ色の部分はα=0にしておけば良いからです。
いま製作中のWAFFLEの『HAPPYほたる荘』で、これをテストしてもらったところ、非常に良好な結果が出たので、『HAPPYほたる荘』の立ちキャラ等は、すべてこの形式で行なうことにしました。
従来、画面のインターフェースはヌキ色の処理が非常に面倒で、グラフィッカーに負担を掛け、かつジャギとの格闘だったのですが、これで完全に解消できたことになります。
Leafの『猪名川で行こう!』にしても、何だかんだ言っても2階調のレイヤマスクしか持っていなかった(と思う)のに対し、αチャンネル付き転送を実装したのは、業界初でしょうか..?そんなわけで、『HAPPY ほたる荘』買ってくださいね^^;
'00/10/19 追加。↑業界初ではなかったらしい(笑) こんなのぐらい簡単なんだから誰かやってるって…。透明部分を白塗りと黒塗りしたものからα情報を抜き出すあたりは斬新だと思ったんだけどな〜。
'00/10/20 追加。PhotoShopからならばαチャンネルを作って、RAW形式で書き出せばαチャンネルも出せるようだ。参考になりそうなところは、ここかな?手間は、上の方法とそんなに変わらないような...
第CC回 シャッフリングとブレインウオッシュ(ユングそして、プロップへ) 00/10/21
ついに第CC回である。CCと言われても、カードキャプターさくらしか思いつかないダメ人間なやねうらおは、CCレモンと言われても頭のなかではカードキャプターれもんと変換されているし、たぶん、そのへんの感性は誰よりもダメな方向に傾倒しているはずだ。(自慢にならん!)
カードで思い出したのだが、麻雀の研究をするために、京王八王子の雀荘に行ってきた。少し麻雀を打ったあと、ゲーセンで麻雀をして、何度かあがると、景品交換カードが出てきた。これが何なのかわからないまま、カードを店員に渡すと、ショーウィンドゥから、ポイント分の景品を選んで良いという説明を受けた。とても丁寧な口調の店員だった。
少し大きめのぬいぐるみで、To Heartのあかりが置いてあったので、やねうらおは『これ』とだけ言った。すると、別のところに連れていかれ、赤だけでなく、緑のもありますが、どちらがいいですか?と尋ねられた。やねうらおを、特別に優遇してくれたようだ。非常に親切でフレンドリーな店員だと思った。赤と緑…。緑というのは、どうやらマルチのことのようだ。『そうですね〜じゃあ、あかりで』 そう言ったとき、自分のとんでもない失言に気づいた。
あか……り?敵愾心剥き出しの店員の目は、なんだ、このオタク野郎!気持ち悪いんじゃボケぇ!という痛烈な批判を内包していた。店員の表情は、さきほどまでの優しい口調からは想像もつかないほどに豹変していた。その態度は、たとえるなら、いままで6年間付き合っていた国家公務員をしている堅実な彼氏が実は下着泥棒で女装趣味で浣腸マニアで幼女嗜好であったことが一挙に発覚したときの彼女のそれであった。さすがに、鈍いやねうらおも、軽い咳払いをして、咄嗟に言いなおした。『あか!でお願いします!』 刹那…もうさきほどまでの店員の険しい表情はどこにもなかった。赤でございますね。かしこまりました。店員は何ごともなかったかのように平然と赤いぬいぐるみをダンボールからひとつ取り出すと、優しくやねうらおに手渡してくれた。そうだ。これでよかったのだ。最初から、そう言えばよかったのだ。
次に来たときには、また麻雀ゲームをして緑のぬいぐるみをもらおう。やねうらおは、そう思った。あの緑のぬいぐるみの名前は何て言うのだろう?やねうらおは過去に知っていたような気もするが、いまや思い出すことすら出来ない。最近、すっかり経営陣が交代し、はぎやまさかげ氏を始めとしてごそっと優秀な人材が抜けおちて、ファンも萎え萎え(反意語:萌え萌え)な会社だったところまでは思い出せるのだが、緑のぬいぐるみの名前だけは思い出せない。きっと思い出さないほうが良いのだろう。思い出したとしても、また忘れたほうが良いのだろう。世のなか知らないほうがいいこともあるのだから―――。
さてさて、今回は数字をシャッフリングについての考察です。よく、ゲームで、ランダムな数字が欲しいことがあります。ただし、数字はそれぞれ1回ずつしか出てきて欲しくはないのです。このようなランダムな数列を得るのに、次のようなプログラムを書きます。
for(i=0;i<N;++i) v[i]=i; // 初期化 |
配列に0...N-1までの数字を代入して、ランダムでM回、2つを抽出して入れ替える。そうすれば、数字の重複は起こらないと、こういう仕組みです。ところで、このときこのシャッフリング回数Mを何回に設定すれば適切か、というのが今回の問題です。上のプログラムをわかりやすく、下のようになおします。
for(i=0;i<N;++i) v[i]=i; // 初期化 |
それでは数が少ないときから考えていきましょう。
☆ N==2のとき、M==1で十分である。なぜなら、1回のシャッフリングで(k1,k2)==(0,0),(0,1),(1,0),(1,1)をそれぞれ等確率で取りうるが、(k1,k2)==(0,0),(1,1)のときは、(v[0],v[1])==(0,1)となり、(k1,k2)==(0,1),(1,0)のときは(v[0],v[1])==(1,0)となるので、v[0]は1/2の確率で0を取るからである。
☆ N==3のとき、M==1では、(k1,k2)==(0,0),(0,1),...,(2,2)をとるが、1回のシャッフリングでは(v[0],v[1],v[2])==(割愛^^)なので、v[0]は5/9の確率で0をとり、2/9の確率で1,2/9の確率で2を取る。よってM==1では不十分である。
N==3のとき、M==2で、v[0]==0である確率は、1回目のシャッフリングで0がその場所に停留する確率(5/9)×2回目のシャッフリングでもさらに停留しつづける確率(5/9) + 1回目のシャッフリングで0がv[1]かv[2]に行く確率(4/9)×2回目のシャッフリングでその0がv[0]に戻ってくる確率(2/9)である
= 33/81である。つまり、M==2でも不十分なのである。
以下、Mをいくら増やしても、数学的帰納法により、1/3にならないことは証明できると思う。(M→∞で、1/3に収束はする)
ということで、まんべんなく散乱するようなシャッフリングは、この方法では不可能なのです。ただ、実用上はそれでも十分だと思うので、もう少し検討を続けます。
ともかくv[i]に、そのままiが停留される確率があまりに高いと困ると思うので、N個の配列ならば、v[i]がシャッフリング対象に選ばれない確率は1/N以下であって欲しいと思います。
一回のシャッフリングで2度の選出(Rand)が行なわれるので、M回のシャッフリングでずっとv[i]が選ばれない確率は、(N-1/N)^2Mで、これが1/N以下になるような最小のMを決定すれば良いわけです。mathematicaでちょっと計算してみました。
Nの値 | Mの値 |
3 | 2 |
10 | 11 |
30 | 51 |
50 | 97 |
100 | 230 |
1000 | 3453 |
10000 | 46050 |
まあ、NとMの関係について統計的な話を仕方ないので、割愛。ともかく、このような2数の交換によるシャッフリングは、お手軽ですがあまり効率は良く無いですし、完全なシャッフリングはN==2のときしか不可能ということで、普通は、こうやるんですよねー。
for(i=0;i<N;i++) v[i] = i; for(i=0;i<N-1;i++) Swap(v[i],v[i + Rand(N-i)]); |
ちゅーか、これつい先日まで知らんかったのです。(笑) 上の考えてて、何回ぐらいにしたら妥当なんだろう?とか思って、よく考えたら、これでいいんじゃんとか(笑) こういうのって当たり前すぎて誰も教えてくれないし、どこにも書いてないから、案外盲点になんだよな〜。
しかし、俺の20数年間のプログラム人生って、一体、なんやったんや…。自己嫌悪。かくなる上は緑のぬいぐるみに慰めてもらうしか…。(ダメ)
第CD回 Boxing and Unboxing (コピーのセマンティクスを巡って) 00/10/24
いま、知り合いに頼まれてコミケに出すためのゲームを作ってます。4才ぐらいの女の子が出てきて、えっちなことをする、ちょっと恥ずかしいゲームなので、私の名前は匿名にしてもらうように頼んであったんです。そして、その匿名ってのが、今日わかりました!何と、その匿名とは、やねうら某…お前、それじゃ匿名になってへんやろ!!なめとんのか!!このゲームは、ナタデココとか、イチゴのショートケーキとかを女児のパンツの中に入れて、ぐりぐりする描写とかあるんですけど、PTAとか婦人団体とかが恐いので、やねうら某だけは勘弁してください(笑)
しかし、実力は大差ないのに原画マンの名前だけで売れたり売れなかったりする、この業界の風潮はなんとかならんのでしょうか。まあ、原画マンもピンキリで、うちの原画マンの場合は、エロゲンガーと呼ばれています。毎日、出社するときは、昆虫をモチーフにした仮面なんかつけてバイクに乗って颯爽と登場し「エロゲンガー参上!」 これですよ、これ。(なんや?特撮ヒーローか?) 「地球の平和はオレが守る!エロゲンガー!」とか言いながら、電話応対とお茶くみ、雑用をこなすわけです。そんななかでも、「アンインストール方法がわかりません」とか言う電話がユーザーからあると「うちのゲームを消すだなんて許せん!エロゲンガーをナメテンカー!」と親切丁寧に応対しています。(どこが親切丁寧なんや?) ユーザー登録ハガキに「絵がどっぺりしている」とか感想に書かれていれば、「オレは明日からエロゲンガー改め、ドッペルゲンガー!」と言ってハガキをこっぱみじんこにしてしまいます。―――って冗談なので本気にしないように(笑)
それはともかく、やねうらおは、ちょっと仕事を抱えすぎなので、このままでは、年内の仕事は、どれかを断らなくてはならないのです。とりあえずは、負担のわりにあんまり儲からないプログラミング本が怪しいかも(笑)
さてさて。今回のお題目はC#についてである。C#については、ASCIIから『C#入門 Microsoftの次世代言語を学ぶ』という本が出ているが、読んでも例がすくなすぎて何を言ってるのかさっぱりわからないので、絶対買わないように。1,900円+税で買って馬鹿を見たやねうらおが言うんだから間違いない(笑) C#について知りたければ、
http://msdn.microsoft.com/net/
のほうから、日本語での言語紹介(WORDで約40ページの文章)と、この本よりはるかに詳しい言語リファレンス(英語:WORDで約280ページ)がダウンロードできる。
C#を実際に使ってみる気はあまりしないのだが、C++の悪い点がかなり改善されていることは注目に値するし、C++の欠点を浮き彫りにしてくれるので、勉強のためには良いかも知れない。また、C++ライクな言語を設計しようという人にとっては、目が離せないだろう。
まあ、やねうらおとしてはNET対応の機能には、特に興味はないし、クラスもJava風かなというぐらいで、特に目新しい箇所は無い気もする。
面白そうなのは、boxingとunboxing、そしてdelegateである。このへん、前述のASCII本ではきちんと説明がされていない。はっきり言って、C#で一番おいしい部分はこの部分のような気がする。そんなわけで、実際にmicrosoftからダウンロードしてきた言語リファレンスを読む。読んでみたところ、delegateは、どうも、ただの関数ポインタ保持クラスっぽいので、たいして面白くはなさそうだった。ポインタを排除したので、関数ポインタを使うための仕組みが必要だったので作ってみたという程度。では、boxingとunboxingについてはどうだろう?(以下の訳文は、やねうらおの手による)
Boxing and unboxing Boxing and unboxing is a central concept in C#’s type system. It provides a binding link between value-types and reference-types by permitting any value of a value-type to be converted to and from type object. Boxing and unboxing enables a unified view of the type system wherein a value of any type can ultimately be treated as an object. boxingとunboxingはC#の型システムのなかで中心的概念である。それは、どんな値型でもobject型へ変換可能にすることによって、値型と参照型との結合を提供する。boxingとunboxingは、最終的にはどんな値型でもobjectとして扱うことが可能になるという点において、型システムに統一的視座を与える。 |
なんや、よーわからんが、object型ってのは、すべての型の基底のようだ。MFCのCObjectみたいな奴?
Boxing conversions A boxing conversion permits any value-type to be implicitly converted to the type object or to any interface-type implemented by the value-type. Boxing a value of a value-type consists of allocating an object instance and copying the value-type value into that instance. boxing変換は、どんな値型でも暗黙にobject型か、値型で実装されたインターフェース型に変換されることを許している。The actual process of boxing a value of a value-type is best explained by imagining the existence of a boxing class for that type. For any value-type T, the boxing class would be declared as follows: 値型の値をboxingする実際のプロセスは、その型のboxingクラスがあると考えればうまく説明できる。class T_Box T_Box(T t) { Boxing of a value v of type T now consists of executing the expression new T_Box(v), and returning the resulting instance as a value of type object. 値vを持つ型Tのboxingは、new T_Box(v) を行なったときと同じで、結果としてobject型の値をインスタンスとして生成する。Thus, the statements そこで、以下の文は、int i = 123; conceptually correspond to 概念的には、以下のものと同じである。int i = 123; Boxing classes like T_Box and int_Box above don’t actually exist and the dynamic type of a boxed value isn’t actually a class type. Instead, a boxed value of type T has the dynamic type T, and a dynamic type check using the is operator can simply reference type T. 上記のようなboxingクラスは実際には存在しないし、boxingされた値の動的な型は実際にはクラスではない。つまりは、型Tのboxingされた値は、動的な型Tを保持し、isオペレータによる動的型チェック(やねうらお注:C++のdynamic_castのようなもの)を使えば、実際にT型を参照することがわかる。 For example, int i = 123; will output the string “Box contains an int” on the console. とやれば、Box contains an intとコンソールに文字列が出力される。A boxing conversion implies making a copy of the value being boxed. This is different from a conversion of a reference-type to type object, in which the value continues to reference the same instance and simply is regarded as the less derived type object. For example, given the declaration boxing変換は、暗黙的にboxingされる値のコピーの生成を行なう。これは、参照型からobject型へ変換するのとは異なる。(よって、同じ実体を参照しているわけではない) たとえば、以下の宣言が与えられ、struct Point public Point(int x, int y) { the following statements 以下の文を実行するとき、Point p = new Point(10, 10); will output the value 10 on the console because the implicit boxing operation that occurs in the assignment of p to box causes the value of p to be copied. Had Point instead been declared a class, the value 20 would be output because p and box would reference the same instance. コンソールには10という値が表示されるだろう。なぜなら、pをboxに代入するboxingの暗黙的プロセスは、pの値のコピーを引き起こすからである。Point構造体がclassと宣言されていたならば、20と出力されるだろう。なぜなら、pとboxは、同じインスタンスに対する参照であるからである。 |
C#では、classは参照型・structは値型である。参照型なので、boxingしても、boxの中身はポインタで、boxingによって行なわれるのは、ポインタ(参照)のコピーだけなので、同じ実体を指すという仕組みのようだ。
An unboxing conversion permits an explicit conversion from type object to any value-type or from any interface-type to any value-type that implements the interface-type. An unboxing operation consists of first checking that the object instance is a boxed value of the given value-type, and then copying the value out of the instance. unboxing変換は、object型から、あらゆる値型、あるいはインターフェース型からインターフェース型を実装したあらゆる値型への明示的変換です。unboxing変換は、まず要求された値型を、boxingしたものかというチェック(やねうらお注:C++のdynamic_castのようなもの)があり、そして、実際のインスタンスのコピーが行なわれる。 Referring to the imaginary boxing class described in the previous section, an unboxing conversion of an object box to a value-type T consists of executing the expression ((T_Box)box).value. Thus, the statements 前の節で書いた説明のために持ち出したboxingクラスについて言えば、object boxをTの値型に変換するunboxing変換は((T_BOX)box).valueという表現と等価である。つまり、以下の文は、 object box = 123; conceptually correspond to 概念的には、以下のものと等しい。object box = new int_Box(123); For an unboxing conversion to a given value-type to succeed at run-time, the value of the source argument must be a reference to an object that was previously created by boxing a value of that value-type. If the source argument is null or a reference to an incompatible object, an InvalidCastException is thrown. 要求された値型へのunboxing変換は、実行時に検証されるため、source argument(やねうらお注:上の例で言えば、(int)boxのboxのこと)は、前もって値型の値をboxingして作られたオブジェクトへの参照でなければならない。もし、source argumentとしてNULLや、互換性のないobjectへの参照が渡された場合は、InvalidCastException例外がスローされる。 |
なんか実りが無いのだけど(笑)、ここまで読んできてわかったことは、C#ではクラスはすべて参照型のようである。object型というクラスはC++のvoid*に似てはいるが、ちょっと違うのは、object型への代入は、boxingと呼ばれ、実体をコピーしたあと、そのコピーされた実体への参照(ポインタ)をもらう。逆に、object型から元の型へのコピーはunboxingと呼ばれ、こちらは元の型への参照(ポインタ)を渡す。unboxingのときに型チェックがあることを除けば、それほど不思議な機構ではない。
問題となるのは、このようなboxingとunboxingの恩恵についてである。boxingには値コピーという操作が含まれるが、boxingはそれに見合うような恩恵をもたらすのだろうか?そもそもboxingとunboxingは何のためにあるのか?言語リファレンスは、そこについては何も教えてくれない。自分で考えなければならないようだ。
いくつか整理していこう。object型・class型・インターフェースは参照である。加えて言うならデリゲート、string、配列も参照である。値型は、単純型・struct型・列挙型である。
まず私見ではあるが、クラスを参照型として扱うのは賛成である。参照にしておけばスライシングの問題は発生しないし、ポリモーフィックな配列を問題なく使える。また、structは値型、classは参照型という区別、これも実践的だし洗練されていると思う。そんななかで、boxingは、値型と参照型への掛け橋となる機構のようだ。つまり、値型、たとえばstructから、参照型すなわちobject型への変換のための仕組みだと言える。unboxingは、この逆に、参照型(object)から値型へ戻す仕組みだ。つまり、参照型であるclass型をboxingするメリットはあまり無いのかも知れない。
では、値型を参照型へ変換するのがboxingだとしたら、それはそんなに便利なことなのだろうか?また、実体をコピーしなければならないようなことなのだろうか?それは、どういう場合なんだろうか?これも、デリゲートと同じくポインタを明示的に扱わないようにするために用意された苦し紛れの機構に過ぎないのだろうか?どうも、この部分が、やねうらおにはよくわからない。これと言った使い道もいまのところ思いつかない。そのへんも実際にC#を使ったわけではないので、よくわからない。本当に、これがC#の中心的概念なの?って感じ。
(つづく…?)
誰かboxingの使い道を教えてくれるとありがたいかも^^;
第CE回 MediaGXはCrusoeの夢を見るか? (Fiva買いました^^;) 00/10/28
最近、このコーナーの趣旨がずれてきている。このコーナーは、何の役にも立たない、精神の開放(解放ではない)を目指す、プログラミング読み物コンテンツ、あるいは、やねうらお日記帳だったはずだ。「とても役に立ちます!」という感謝のメールを頂戴するごとに、なんでやねん!と突っ込み続けて300年(いや、そんなに経っちゃいないって…)そろそろ原点に立ち返ったほうがいいのかも知れないとか思ったり思わなかったりするわけだ。
そんなわけで、最近の出来事についてだらだらと書いてみる。自分の使っているパソコンのハードディスクが満杯になった。28GB+12GB=40GBが溢れたのである。ほとんどはCD−Rにバックアップをとってあるので飛んでも大打撃にはならないが、それでもCD−Rからいちいちデータを取り出していたのでは仕事にならないので保存する場所が必要である。そこで、緑のぬいぐるみを捕獲するために京王八王子に遊びに行ったついでに、ヨドバシカメラで60GBの内臓ハードディスクを買った。確か、27,800円だったと思うが、10%のポイント還元があるので、実質25,000円だと考えて良いだろう。これを持ち帰ったのだが、あいにくCD−RWをIDEで増設しているので、すでにIDEに空きがない。おまけにUltraATA100なので、いつも使っているパソコンのマザーボードでは認識すらしない。BIOSをアップデートしようにも、マザーボードのメーカー名すらわかんないときたもんだ^^;
何がウルトラなのかは知らないが、ウルトラと言われてもウルトラマンしか思いつかないし、ATAと言われても「あたぁ〜」としか読めないやねうらおは、「あたたたたたぁ〜ほわちゃ!」と叫んで、隣で作業をしているグラフィッカーのみずさわひろのパソコンに許可もなく取り付けてやった。みずさわひろは、交通事故で両親を亡くした友達を見るような哀れみの目でやねうらおのことを見ていた。
やねうらおにとって、そんなことはおかまいなしだ。ドンマイドンマイ。オレには緑のぬいぐるみがついている。そう自分に言い聞かせて、とりあえず、ひろひろマッシ〜ン(やねうらお命名)に、ネットワークごしにアクセスするのだ。一応、100BASEで接続されてはいるのだが、大量のデータがあるので、ちっとも早くない。そこにぎゅうぎゅうと詰め込むこと、6時間。やはりデータを取り出すのも早くはない。28+12+60GB=100GBの100ギガショック!になるんとちごたんか。ショックは受けるには受けたが、それは遅すぎるという衝撃だけだった。
そんなわけで、作業用のノートをもう一台買おうと決心し、またもや次の日、ヨドバシカメラに足を運んだわけだ。狙い目はソーテックだと思っていたのだが、ソーテックのe−noteのキーボードはいかにも安っぽい。ソーテックなんぞ、もともと大手にパソコン部品を卸していた部品配給メーカーに過ぎないくせに、いつの間にこんなにでしゃばってきやがったんだ、ぴしぴしぴし。(鞭を打つ音) そもそも3kgなんて常時持ち運びするには少し重い。主流は1.5Kg前後。シャープのメビウスノートがこれを達成しているが、同性能の日立やIBM製品に比べて3万ぐらい高い。ブラックTFT液晶がなんぼのもんかは知らんが、同性能で3万も差があると買う気になれんわい。
IBMのThinkPadは13万弱だが、こいつは却下。コストパフォーマンスがソーテックと同じぐらいだとしても、ESCキーがF1の上にあるデザインは生理的に嫌だし、デザインは最悪。平仮名の「ろ」が左下にあるakiaのノートパソコン(誰も知らんて…)みたいなもんです。ソフトウェアでキーボードを入れ替えればよさそうなもんですが、やっぱり3Kg強あるのでは持ち運べないし、分厚さもかなりのもんだし…。
さ〜さんが使ってるPanasonicのLet's noteは、2.5インチハードディスクが搭載できる厚みがあるわりには、1.53Kgで100BaseのLANコネクターが付いてたりするんで、なかなかよさげなのですが安いモデルが在庫なしときたもんだ。
新しいVaioはCrusoeを載せていて駆動時間で魅力はあるものの、命令を実行前にソフトウェアデコーディングするのでどれくらい遅いのかよくわからなかったのと、値段は20万前後、しかも在庫なし。
そう考えていたときに目にとまったのが、CasioのFivaの最新機種です。店頭価格99,500円。(ヨドバシカメラはポイント10%還元なので、実質9万と見て良いでしょう) MediaGX(cyrixの省電力対応PentiumMMX互換チップ)233MHz搭載、6GBという大容量、840グラムという軽さ、64MBメモリ、Windows98SE。何より、キーがそれなりに大きいので入力しやすいし、Casioが独自に開発した800×600の液晶。モバイルタイプのVaio、リブレットでさえ、縦は480が限界だったのに、このサイズで600もあるんですか?もうこれは買いっきゃないでしょう。一応、カタログチェックを..えっ?カタログ無いんですか?ひとつ古いタイプのならある?バッテリは標準バッテリで3時間。オプションの大容量バッテリで6時間。まー3時間もあれば十分かな。(ちなみに大容量バッテリは23,000円とのこと)
そんなわけで速攻ゲットしたんすよ。
きっと、やねうらおと同じように考えた人が、大量に居たんでしょう。Fiva馬鹿売れの記事がZDNetでも書かれています。
http://www.zdnet.co.jp/shopping/0010/26/w_tnpcimp.html
CD-ROMとFDは無いので、とりあえず100BASEのTypeIIのLANカードをいま加算してもらったばかりのポイントを使って購入。とりあえず、家に持って帰るまで気がつかなかったんですが、このネットワークカードのドライバ、どうやってインストールすんねや!!FDは別売り、しかもポートリプリケータが必要。合わせて2万ちょいするんだよなぁ…。よし、赤外線や。Palm同士でしか使ったことないけど赤外線で飛ばすことにした。そんなわけで眠っているノートパソコン(Pentium133MHz)から、赤外線でファイルを送信!マシンの間は60cmほど離れていたんですが、何なくキャッチできました。凄すぎ。これ、電波でないから電波妨害/傍受されないし、やっぱ産業スパイとかは、機密情報を赤外線でやりとりしてんのかなぁ..(60cmしか離れてないんなら、口で言え!とかいう話もある)
そんなわけで、ソフトをいろいろインストール。まあ6GBもあるんで、容量には困らないが、マシン遅すぎ。これ、体感速度ではPentium133MHzぐらいなんだけど、どうなってんのー?おそらく、極小ハードディスクが異様に遅いのだろう。本体自体はPentiumMMX166ぐらいな感じかなー。おまけに、LANカード抜いたら、カードに黒いインクのようなものがついてるし..どうも熱で中の何かが溶け出したようだ。おい、大丈夫なんか?危なすぎ。これクレームつけたら、回収もんではないだろうか…。次に、バッテリーテスト。フル充電した状態で、MP3をプレイして放置。これが本当の放置プレイだ(なんのこっちゃ)
結果、1時間20分しかもちませんでした(汗)
なめとんのかと言いたい。HDDアクセス+サウンドでの連続稼動は確かに公正なテストでは無いかも知れない。それならば、HDDアクセスをせず(ただし停止はさせず)、バックライトも最低まで落とし、サウンドはもちろん再生せずで、テストしてみると…。
結果、1時間40分しかもちませんでした(大汗)
えー。これって詐欺ですか?確か、パンフレットに載っていた古い機種はMediaGX233ではなく200で、メモリは64MBではなく32MBだったような気はする。その差ですか?200を233にして、32を64にしたらバッテリー時間は半分になったんか?そんなことが許されていいのか?それでパンフレット出せずにいるんか?ひょっとして、この機種はひそかに回収してるんか?ヨドバシカメラの店員が、嫌そうに「えーホントに買んですかー」という顔をしていた理由がいまわかったような気がするぞ。
そうは言うものの、840グラムで、Windows98SE、64MB、6GB、56Kbpsモデム内臓というのは魅力だ。広辞苑の電子辞書と、Mathematica4を入れて持ち歩いて使っているが、結構便利である。安くで売られていたら買ってはいかが?という感じ。
ついでに、Fivaの後継機が発表になっていた。11月末に発売になり、MediaGX300MHzを搭載し、10GBで840gだそうだ。そっちにしたらよかった(笑) しかしやねー、調べてみると、その後継機のバッテリパックも、品番は同じなのよねー。ちゅーことは、同じバッテリを使うわけで、クロックが上がった分だけ、さらに駆動時間は減るはず。となると、どう考えても1時間ぐらいしかもたないはずである。それを3時間と書いて「使用環境によって異なります」はないんとちゃうかなー(笑) Vaioもそうだけど、省電力モードでの時間を書くなっちゅーに。実質、ノーパソは、メーカー公称値の半分ぐらいの時間しかバッテリはもたないということか…。
さてさて。前回の続き。BoxingとUnboxingの使いみちですが、やねうらおには結局わかりませんでした^^;
structを値型としてしまったがために、すべての型基底であるobject型(参照型)への変換するためにBoxing/Unboxingという機構が必要になったのかも知れません。すべての型基底としてのobject型の導入は賛否分かれるところで、Javaではテンプレートを導入しない代わりにObject型でそれを代用しようとしていましたが、C#のobject型も、それに近いものがあるのかも知れません。
とゆーか、テンプレートをどうして廃止したのか謎です。delegateの機構を見る限りは、Javaより洗練されていると思ったのに、テンプレートを無くしてしまうとC++からはダウングレードのような気がしてなりません。Windows95→98→SE→MEと進化するごとに遅くなり続けるくせにどこがアップグレードやねん!と突っ込みたくなるMicrosoftのすることなので、人智では到底理解不可能なのかも知れません^^;
第CF回 これからはANTIALIASED_QUALITY(アンチェリ物語完結編^^;) 00/11/02
やねうらおは、いまだ半そでシャツ一枚なんですよ。それと言うのも長袖の服とかシャツとか持ってないんですよ。それというのも金が無いからですとも。ええ。会社のみんなからは、ハードディスクとかノーパソ買う金があるんなら、冬服を買え!と突っ込まれまくってますが、ハードディスクとかノーパソを買う金はあっても服を買う金は無いんですよ。わかりますか?わかりますよね?(笑)
それと言うのも、上京してくるときにこちらに冬服を持ってきていなかったからなんですが…。送ってもらおうと思って実家に電話しました。
「あんた、ええとこに電話してきてくれたわ」
なんや?ええとこやったんか?それは、邪魔して悪かったな。オレもテレビドラマ見ててええとこで電話かかってきたらムカっとくるもんな。ほなしゃいなら〜。
「違うがな。ちょっと、相談があんねや」
なんや、相談かいな…。わかったよ。何でも言うてよ。
「それというのも、儒珍トイレがあふれたんや」
えっ!そりゃ大変や。水びたしやな。詰まったんか?キュポキュポせな!
「ちがうがな。儒珍トイレがいっぱいなんや」
せやから、水びたしなんやろ?キュポキュポしたらええがな。
「水は出てへん〜」
なんや?その、儒珍トイレってのは、水洗式とは違うんか?ついに我が家も流行りのシャワートイレとかになったんかと思ったぞ。しかし、うちのトイレ、いつの間に儒珍式になってしもたんや?
「ちがうがな。パソコンで、メール入ってくるトイレがあるやろ?」
えっ?メール入ってくるトイレって何や...
「あれが、いっぱいなってしもたんや」
メール入ってくるトイレ、メール入ってくるトイレ・・儒珍トイレ、じゅちんトイレ、じゅしんトイレ・・・それは受信トレイやろ!!何が儒珍トイレなんや、はっきりしゃべれ!はっきり!しかも、トイレじゃなくて、ト・レ・イ。たちつてとのトに、らりるれろのレ、あいうえおのイで、ト・レ・イ。受信ト・レ・イ。おわかり?
「そうとも言う」
そうとしか言わへんちゅーの!
そんなやりとりのあと、冬服を送ってくれるように頼むと…
「ああ?冬服?間違って捨てたわ。ごめんごめん」
捨てたって…何をどう間違ったら捨てられるかな?
「夏にあんたの部屋、掃除してたんや。そしたら、あんたの冬服出てきてな、いま夏やし、これいらんやろとか思って、捨てたわ。ごめんごめん」
ごめんごめんって…(笑) そりゃ夏に冬服はいらんやろけど、捨てたって、どういうこと?もう無いんですか?
「また買ったらええやん。たんと〜儲けてんねやろ?」
そりゃ、服ぐらい買えるけどやなぁ、馬車馬のように働いてやっと手に入れた金やで!毎年勝手に服、捨てられたんではたまらんがな!
「服はよ買わんと風邪ひくで」
誰が捨てたんじゃい!誰が。まさか捨てたとは思わんかったわい。
「儒珍トイレの件、ありがとーな」
だから、儒珍トイレと違うゆーてるやろ…。
うちの母親は、かなり困った人である。たぶん頭のネジが16本ぐらい足りないのだろう。
さてさて。今回は、文字フォントのクオリティの話です。::CreateFontでフォントを作成するときに指定できるのは、DEFAULT_QUALITY(ディフォルト)とDRAFT_QUALITY(それなり)、PROOF_QUALITY(ばっちぐー^^;)の3つだと思われていますが、それは違うのです。実は、隠しオプションで、NONANTIALIASED_QUALITY(アンチェリなし)とANTIALIASED_QUALITY(アンチェリ有り)というのがあるのです。
どうも、Windows95+MicrosoftPlus!の入っている環境で動くらしいのですが(Windows98以降ならば、Plus!なしで使える)、こいつがまた困ったやつなのです。そもそも、私は自分のWindows2000マシンでPROOF_QUALITYでテストしていたのですが、ルーペで拡大して見たら勝手にアンチェリがかけてあるのです。んなアホな…と思い、別のWindows2000マシンでテストしたところ、そちらではアンチェリがかからないわけです。
調べてみると画面のプロパティ→効果→「スクリーンフォントの縁を滑らかにする」をONにしていると、勝手にアンチェリをかけるようなのです。はっきり言って余計なお世話で、勝手にアンチェリをかけられると、アンチェリ部分はそのときの背景色(yaneSDK2ndのCDIB,CPlaneでは黒)とブレンドした色になるので、それをどこかに描画しようとすると黒が混じってくるのです。
アンチェリなしにするには、「PROOF_QUALITY | NONANTIALIASED_QUALITY」とすればよさそうなものですが、これではうまくいかないのです。なんでかな〜と思って、WinGDI.hを見てビックリ仰天!(死語)
WINGDI.H(1185): #define DEFAULT_QUALITY 0 WINGDI.H(1186): #define DRAFT_QUALITY 1 WINGDI.H(1187): #define PROOF_QUALITY 2 WINGDI.H(1189): #define NONANTIALIASED_QUALITY 3 WINGDI.H(1190): #define ANTIALIASED_QUALITY 4 |
うひゃー。やってくれました(笑) 見ての通り、併用不可なのです。ちゅーことは、たとえばNONANTIALIASED_QUALITYを指定したとき、クオリティは、PROOFなんか、DRAFTなんか、どれやねん!?って思うわけです。困った奴です。そもそも、PROOFとDRAFTの差なんか画面で見てもわかりません。本当に変わってるの!? 謎は深まるばかりです。
考えても無駄っぽいので、とりあえず、アンチェリして欲しいときはANTIALIASED_QUALITY、アンチェリされると困るときは、NONANTIALIASED_QUALITYを指定するしかなさそうです。アンチェリしたかったら、すれば〜?ってときだけPROOF_QUALITYかDRAFT_QUALITYか、DEFAULT_QUALITYを選ぶという使いわけになりそうです。そんなことは、オンラインヘルプに書いとけ!って感じです。
で、さらに実験してるとANTIALIASED_QUALITYを指定しているのにアンチェリがかからない状況が発生。どうも調べてみると、フォントサイズが24以下だとアンチェリがかからないようです。しかも他の環境で試すと、25以上でもアンチェリがかからないときたもんだ。うーん。困ったやつだなぁ…。
こうなってくると、NONANTIALIASED_QUALITYで描画して自前でアンチェリをかけるしか無さそうです。とりあえずぼかし(yaneSDK2ndのCDIB32::ShadeOff)をかけて、それをα値として転送してみたんですが…どうも下手くそ〜な文字に見えます(笑) だめだこりゃーってことで、正確に縦、横2倍のサイズで描いて、それを縮小するとき、すなわち2×2ドットが1つのドットになるわけで、その4つのドットの加重平均をα値にすればよさそうです。確かにこの方法はうまく行くのですが、正確に縦、横2倍サイズで描くのは不可能なのです。なぜならば、フォントサイズとして2倍を指定したからと言って、文字サイズが正確に2倍になるわけではないからです。まあ、2倍のフォントサイズを指定したときに得られるTextExtentPointの半分の大きさになると考えれば良いのかも知れませんが、アンチェリ文字のときだけ生成されるプレーンのサイズが違うのもまた気持ち悪いのです。
そのへんを適当にごまかしたのがyaneSDK2nd1.00β3のCTextDIB32::UpdateTextAです。動いてるのは、yaneSDK2ndのサンプル13がそれです。