2007 年 8 月 31 日 18 時 29 分

C++/CLI #2: ハンドル ^ とハンドル演算子 %


このアーカイブは同期化されません。 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 メソッドに渡さなければならないのは、
オブジェクトそのものではなくて、参照である。
オブジェクトの参照(ハンドル)を得るために、
ハンドル演算子 % を使っているのである。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.