過去の独りごち/独りごとはこちら
過去のJavaアプレットは
こちら

 

10/8 VariantKnife

 ケフカも1ターンKOです(酷

 …じゃなくて(ベタ

 Falcon氏の情報により、Delphi6ではなんと、全Delphiユーザ待望演算子の多重定義が出来るという事で! そんなん気がつかなかったぜ!と焦るとともにCマガも読まないとなぁ。ネット徘徊だけだと知識が偏ることを再確認。確認するだけで反省して具体的な行動に移さない辺りアレですが。だって本屋さんが近所に無いないんだもん。

 話を元に戻すと、実際には演算子の多重定義というトピックは無くて、カスタムバリアントというトピックになってて、こりゃ分かりません。 C++のようにクラスならなんでもかんでも演算子の意味をこじつけられるのではなくて、バリアント型についての拡張だけなんですね。 確かにコンパイラの変更を最小限にして演算子の多重定義のメリットを得るためには、そういう手段が良かったのでしょう。

 しかし…ゲーム関係のコーディングをしてるヒトには、バリアント型を使う事自体珍しいと思います。バリアント型って速度を重んじる向きには大敵ですから。ともかく、演算子の多重定義という、Delphiユーザにとって長年の夢の果実、その皮だけでも手に入ったのだから味わってみようじゃないですか!

 

 まず、ともかくヘルプを読み読み…先にも述べたように、カスタムバリアントをヘルプで検索すると情報が得られます。

 そもそもカスタムバリアントとは何かというと、実行時に型をコロコロ変えられる、VisualBasicには昔から会った、デバッグを困難にするイヤなデータ型のことです(身もフタも無い…) JavaScriptをいじった方ならば、JavaScriptの変数みたいなもの、といえば分かるでしょうか? で、従来までのバリアント型では、実行時に型を変えられるといっても int, real, string などの単純型のどれかになれるというだけでした。確かに有ると無いとでは大違いかもしれませんが、どんな型にも…複素数やベクトルや行列など、ユーザの自由に定義した型に変身できるようになったのが、Delphi6のバリアント型なのです。 即ち、僕らはこれからバリアント型の変身機序について学ぼうというわけです。ヒーローたる者、変身くらいは出来ないとしまりませんので、一丁勉強してみるとしましょう。 Delphi7では合体がサポートされるといいですね(ぉ

 ヘルプに話を戻して、カスタムバリアントの作成は…以下のような手順で行うそうです。

  1. バリアント型データの記憶域を TVarData レコードにマッピングします。
  2. TCustomVariantType から派生するクラスを宣言します。新しいクラスで,必要な動作をすべて実装します(型変換規則を含む)。 
  3. カスタムバリアントのインスタンスを作成するユーティリティメソッドと,そのインスタンスの型を認識するユーティリティメソッドを記述します。

 1が良く分からないので、サンプルコードを読みつつ理解を進めていくことにしました。Professional版だとサンプルコードの全体である、VarCmplxユニットのソースが入っているので、それを見ると非常に分かり易いんですが、Personalユーザの皆さんは我慢しましょう(^^;)

 一応、テスト用にごく簡単に、ほぼ最低限の要件だけ満たしたカスタムバリアントとして、VarVector3Dという3次元ベクトルを表現するバリアント型を定義してみました。


vartest.zip

(ソースのみ 2.65K)

 VarVector.pas で、VarVector3Dを定義しています。…ソース見ながら以下を読んでやってください。

 ところで、さっき「良く分からない」と書いたバリアント型データの記憶域を TVarData レコードにマッピングとは

  • これから作るカスタムバリアントにどんなデータが詰まっているかを示したレコードを一丁、定義する

 という事のようです。なんの事はない。

 次に、TCustomVariantType から派生するクラスを宣言します。TCustomVariantTypeから派生するクラス、こいつが演算子の多重定義を実現するためのカギになるクラスのようです。直接インスタンスを作ることは無いのですが、VarType毎に演算子の挙動を変える必要があるのは必定なので、このクラスのメソッドという形で実装することになっているようです。どういう具合にこのクラスのメソッドが呼ばれるか、という事まではまだトレースしていません(^^;)

 で、TCustomVariantTypeは抽象クラスなので、必ずオーバーライドしないといけないメソッドがあります。 Copy と Clear です。 Copyは代入などに伴う値の複製、Clearはカスタムバリアント型が長い文字列などの、動的なメモリの確保を伴って確保されている場合の、そうしたメモリの解放などを行うためのメソッドだそうです。 ともかく、変数としてあるためには必須な事ですね。

 他に、演算子の動作を意のままにねじまげるために、BinaryOpメソッドなどをオーバーライドします。抽象メソッドでは無いので、してもしなくても構わないのですが、やらないことには今回の議論の意味はないのです(w

 あとはキャストのためのCast、CastToメソッドのオーバーライド、でしょうか。↑にあるVarVector.pasでは、サボってやってません(^^;)

 最後に、スタムバリアントのインスタンスを作成するユーティリティメソッドと,そのインスタンスの型を認識するユーティリティメソッドを記述します。

 前半分は、代入とかの時の利便性を図るルーチンを作るという事ですが、後ろ半分は

  • これから作るカスタムバリアントのVarType (どんなデータが詰まっているのかを一意に識別するためのWord値) をアプリケーション全体から見えるようにする

 という重要な意味を持っています。このVarTypeの宣言の方法がなかなか面白いというか強引というか…。 VarVector.pasのinitialization、 finalization部まで丹念に眺めてみてください。

 

 さて、こうしてヲレだけのバリアント型を定義したのですが…なんていうか…使いづらいです(^^;) ↑のアーカイブ内のVarVector3D使用サンプルである、unit1.pasを見てください。そう、構造型として使いたいけどメンバにアクセスするためにはいちいちキャストしなきゃならないんです。 これを回避する方法は、それなりに分かり易くDelphiのヘルプに書いてあるので、それを参照しましょう。

 結果だけ言うと、カスタムバリアント型にプロパティを付け加えることが出来るんです。 …けど、このなんともいえない気色の悪い実装は何なんでしょうか(^^;) ソースに現れるメンバ名がメソッドへの文字列として渡される様がなんとも薄気味悪い…。なんだかALGOLの名前による参照が現代に復活したかのようです。

 こんなエレガントさの無い方法を採るんだったら、もっとC++の演算子の多重定義みたいに徹底した進化を遂げられなかったのかなぁ、と思いましたm(__

 

 なにはともあれ、Falconさん、興味深い情報をどうもありがとうございました!ゲーム作成に供する事はあまり無いかとは思いますが、将来的にはどう化けるやも知れません。

 

Taku Hayase(SANDMAN)

戻る