参照クラス
参照クラスの宣言とインスタンス化
C++/CX が対象とする Metro スタイルアプリケーションは Windows Runtime を基盤としています。Windows Runtime は COM を再利用・再設計した新しいアーキテクチャで、プログラミング言語やアプリケーション実行基盤に依存しないコンポーネントの規約であると同時に、動的言語からも利用できるような高い柔軟性を持ちます。よって、プログラミング言語に関わらず、規約に従って Windows Runtime コンポーネントを作成すれば、Windows Runtime に対応するあらゆる環境で共有できます。背景には細かい COM に基づく規約がありますが、C++/CX を使えば標準 C++ のクラス宣言とほぼ同じ形で Windows Runtime コンポーネントを作成できます。
C++/CX の型システムには参照型と値型があります。参照型は標準 C++ における new 演算子で作成したインスタンスのように、プログラムが実体へのポインタを保持します。これに対し値型は、ポインタや参照ではない単純な値そのものを表します。標準 C++ ではクラスや構造体をローカル変数などの単純な値として利用することも、ポインタを通じて参照することもできました。しかし C++/CX の型は明示的に分離されており、参照型のことを参照クラスと呼び、値型のことを構造体と呼びます。参照クラスのオブジェクトは常に参照であり、構造体のオブジェクトは常に値です。
参照クラスは内部で参照カウンタと呼ばれる整数を保持します。参照カウンタはオブジェクトへの参照を別の変数や関数のパラメータなどに複製したときにインクリメントされ、変数が使われなくなったときにデクリメントします。これで、インスタンスを利用している変数を数え、自分を参照する変数が存在しない、すなわちプログラムから参照されなくなった時点で自動的にメモリから解放されます。
標準 C/C++ 言語による開発では、実行時にヒープ領域(すなわち new 演算子や malloc() 関数で生成したオブジェクト)に割り当てたメモリ領域は、不要になった時点で解放しなければなりません。何らかのリソースの解放を忘れてしまうと、プログラムはメモリを占有し続けてしまうメモリリークに繋がります。しかし Windows Runtime の参照クラスであれば、オブジェクトは C++/CX によって管理されるため、明示的に破棄する必要がなくなります。また、C++ 以外の言語で作られた Windows Runtime コンポ―ネントを利用できるのも大きなメリットです。
参照クラスを宣言するに ref キーワードを用います。
アクセス修飾子 ref class クラス名 クラス修飾子 : 継承リスト { クラス本体 };
アクセス修飾子 ref struct クラス名 クラス修飾子 : 継承リスト { クラス本体 };
アクセス修飾子には、クラスの可視性を指定します。クラスを公開する場合は public、公開しない場合は private を指定します。省略した場合は private が既定となります。
クラス名は C++ と同様に宣言するクラスの名前を指定します。C++/CLI ではクラス名の直後にクラスの修飾子を指定します。純粋仮想メソッドを持つ抽象クラスなどは、修飾子で明示的に宣言することができます。クラス修飾子について、詳細は後述します。
以降は標準 C++ 同様にクラスを記述します。フィールド(メンバ変数)やメソッド(メンバ関数)の書き方も同様です。通常の、管理されない標準クラスとは様々な違いもありますが、基本的には参照クラスのオブジェクトの寿命が共通言語ランタイムによって管理されるという点を除いて、クラスとしての機能は標準 C++ と同じだと考えてかまいません。
例えば、参照クラス T を定義するには次のように記述します。
ref class T { };
ref クラスのインスタンスは、標準 C++ の new 演算子では生成できません。new 演算しで参照クラスをインスタンス化しようとした場合はコンパイルエラーになります。参照クラスのインスタンスを生成する ref new 演算子を使います。
ref new 型 (引数リスト)
基本的な使い方は new 演算子と同じです。ref new に続いて参照クラスの型を指定します。引数リストにはコンストラクタのパラメータに渡す値を指定します。パラメータを受け取らないコンストラクタを起動する場合は省略することができます。
上の参照クラス T をインスタンス化する場合は、以下のように ref new 演算子を用います。new 演算子を用いた場合はエラーになります。
new T(); //エラー ref new T(); //OK
参照クラスのコンストラクタや配列について、詳細は後述します。この場では明示的なコンストラクタを持たない単純な参照クラス型のインスタンスを生成してみましょう。
#include <iostream> ref class T { public: void M(); }; void T::M() { std::wcout << L"Stand by Ready!\n"; } int main() { (ref new T())->M(); system("pause"); return 0; }
コード1は ref キーワードを用いた参照クラス T を宣言しています。ref キーワードで修飾されている参照クラス型は new 演算子でインスタンス化することはできません。参照クラスは ref new 演算子を用いてインスタンス化しなければなりません。逆に参照クラスではない標準クラスは ref new 演算子でインスタンス化することはできません。
class と struct
標準 C++ では、クラスの宣言に class キーワード、struct キーワード、及び union キーワードを使うことができました。union キーワードによるクラスはメンバの記憶領域を共有するという点で class と struct キーワードによるクラスとは異色でしたが、class と struct キーワードを使ったクラス宣言の意味はほぼ同一でした。
C++/CX による参照クラスの場合も class キーワード以外に struct キーワードを使ってクラスを宣言することができます。class と struct キーワードの違いは、アクセス修飾子を省略した場合の既定の可視性のみです。class キーワードで宣言されたクラスの場合、既定の可視性は private であると解釈されますが struct キーワードで宣言されたクラスは public であると解釈されます。
また、参照クラスは union キーワードで宣言することはできません。
#include <iostream> ref class A { void M() {}; }; ref struct B { void M() {}; }; int main() { (ref new A())->M(); //エラー (ref new B())->M(); //OK system("pause"); return 0; }
コード2で class キーワードと struct キーワードそれぞれで宣言したクラスの既定の可視性を調べることができます。class キーワードで宣言されている A クラスの既定の可視性は private ですが、struct キーワードで宣言された B クラスの既定の可視性は public となっています。
main() 関数で、それぞれのクラスのインスタンスを作成し、アクセス修飾子を省略して宣言をしている M() メソッドにアクセスしています。A クラスのオブジェクト (ref new A)->M() は private であると解釈されているため、ここでコンパイルエラーが発生します。これに対して (ref new B)->M() は public であると解釈されるため外部からのアクセスに問題はありません。
既定の可視性を除けば class と struct の違いはありませんが、習慣的に標準 C++ の時代から struct キーワードは単純なデータ記憶用のクラスを作成するために使われました。こうした型のことを POD (Plain Old Data) と呼びます。C++/CX において参照クラスの宣言で struct を使う理由はないので通常は class でよいと考えられますが、習慣から struct を使う開発者もいることでしょう。