このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
いきなり出てきた % の文字。これはなんだろうか。
どう見ても剰余演算子の % ではない。
コードを再掲してみよう。
void Dispatcher::Configure(HWND hdlg) {
// スクリーンセーバーを作成
HelloSaver saver;
// HWND のラッパーを作成
HwndWrapper window(hdlg);
// 設定画面を呼び出す
saver.Configure(%window);
}
答えから書くと、この % はハンドル演算子と言い。
アドレス演算子 (&) のマネージ版にあたるものである。
アドレス演算子 & は、オブジェクトが格納されている、
メモリアドレスを得るための演算子であり、
その値はポインタ型 (T *) となる。
ハンドル演算子 % は、マネージオブジェクトを指し示す、
固有の識別子を得るための演算子であり、
その値はハンドル型 (T ^) となる。
ということは、Configure メソッドは、
引数としてハンドル型を受け取るということになる。
では、一体、ハンドル型とは何者か。
これは、ISaver のシグネチャを見れば分かりやすい。
まず、C# での ISaver の定義を見てみよう。
public interface ISaver {
void Configure(IWin32Window owner);
void Create(IWin32Window window,
int width, int height);
void Destroy();
void Paint(Graphics g, Rectangle bounds);
}
これを C++/CLI で表すと以下のようになる。
public interface class ISaver {
void Configure(IWin32Window ^owner);
void Create(IWin32Window ^window,
int width, int height);
void Destroy();
void Paint(Graphics ^g, Rectangle bounds);
};
比べてみると、IWin32Window や Graphics には、
^ という記号が追加されていることが分かる。
これらがハンドル型と呼ばれるものである。
また、よく見てみると、int や Rectangle には、
特に何もついていないことも分かる。
これらの違いはどこから生まれてくるのか。
実は、C++ と C# などの高級言語では、
クラスの扱いが根本的に異なるのである。
型には、大きく分けて 2 つの分類がある。
それは、値型(構造体)と参照型(クラス)だ。
値型は、オブジェクトそのものを表す直接型であり、
参照型は、オブジェクトを指し示す間接型だ。
値型の変数は常にオブジェクトと一体であり、
変数が宣言されると共にオブジェクトが割り当てられる。
別の変数に代入するとコピーが作られる。
この場合、変数とオブジェクトは常に 1 対 1 である。
参照型の変数は、オブジェクトとは独立している。
変数には、好きなオブジェクトを 1 つ代入できるが、
オブジェクトそのものがコピーされるわけではなく、
単に、そのオブジェクトを指し示すようになるだけだ。
そのため、複数の変数が同じオブジェクトを示すことがある。
この場合、変数とオブジェクトは多対 1 である。
また、特例として何も指し示さない状態も表現できる。
高級言語では、上記の差異を言語機能に隠蔽し、
値型も参照型も同じ構文で利用できるようになっており、
参照型が、オブジェクトを間接的に参照していることは、
構文上では意識しなくても良いようになっている。
C++ では、言語機能にそのような差がなく、
通常の変数は全てオブジェクトそのものを示す。
参照型を表すためには、ポインタ等の間接構文が使い、
参照しているということを明示する必要がある。
上記における int や Rectangle は値型であるため、
C++/CLI でもそのまま利用できるが、
IWin32Window や Graphics は参照型であるため、
C++/CLI では、間接構文が必要となるのだ。
そのために、ハンドル型 (T ^) が存在するのである。
では、ハンドル型とポインタ型はどう違うのか。
ポインタが示すのは、アンマネージオブジェクトで、
ハンドルが示すのは、マネージオブジェクトである。
間接的にオブジェクトを示すことに変わりはない。
ただし、ポインタはメモリアドレスを指し示すが、
ハンドルは、特定のアドレスを指す数値とは限らない。
CLI には自動的なメモリ管理機構が含まれているため、
不定期にガベッジコレクションが発生する可能性がある。
そのため、マネージオブジェクトは、
特定のメモリアドレスに常に存在する保証はないのだ。
そのため、ハンドルは、ポインタと異なり、
加算や減算などのアドレス計算はできない。
数値である保証すらない、未知の値と考えるべきである。
では、上記コードでハンドル演算子 % を使う理由はなにか。
HwndWrapper window(hdlg); という構文は、
HwndWrapper オブジェクトを作成し、
window という変数に関連付けている。
C++ の構文規則に従い、変数は常にオブジェクトと一体だ。
window は、参照ではなくオブジェクトそのものである。
C# ではこのような表現はできない。
Configure メソッドに渡さなければならないのは、
オブジェクトそのものではなくて、参照である。
オブジェクトの参照(ハンドル)を得るために、
ハンドル演算子 % を使っているのである。