WisdomSoft - for your serial experiences.

参照クラス

マネージ型のクラスを C++/CLI で実装します。基本的なクラスの書き方は標準 C++ と同じですが、クラスの宣言時に ref キーワードを指定し、インスタンス化には gcnew 演算子を用いなければなりません。

参照クラスの宣言とインスタンス化

マネージコードを利用するメリットは、豊富な機能を備えた .NET Framework の標準クラスライブラリがありますが、同時に管理された型を利用することができることも大きな魅力です。ネイティブ C++ のクラスとは異なる、.NET Framework 専用のクラスを参照クラスと呼びます。マネージクラスと呼ばれることもありますが、現在では参照クラスのほうがより正しい表現だと考えられます。

C/C++ 言語による開発では、実行時にヒープ領域(すなわち new 演算子や malloc() 関数で生成したオブジェクト)に割り当てたメモリ領域は、不要になった時点で解放しなければなりません。何らかのリソースの解放を忘れてしまうと、プログラムはメモリを占有し続けてしまうメモリリークに繋がります。しかし、.NET Framework 専用の参照クラスを宣言すれば、このクラスのオブジェクトはランタイムによって管理されるため、明示的に破棄する必要がなくなります。オブジェクトが不要になった時点で、共通言語ランタイムが自動的に解放してくれるのです。また、C++ 以外の言語で作られた .NET Framework アプリケーションから利用することができるという点も大きなメリットになります。

参照クラスを宣言するに ref キーワードを用います。

参照クラス宣言
アクセス修飾子 ref class クラス名 クラス修飾子 : 継承リスト { クラス本体 };

アクセス修飾子には、クラスの可視性を指定します。クラスを公開する場合は public、公開しない場合は private を指定します。省略した場合は private が既定となります。

クラス名は C++ と同様に宣言するクラスの名前を指定します。C++/CLI ではクラス名の直後にクラスの修飾子を指定します。純粋仮想メソッドを持つ抽象クラスなどは、修飾子で明示的に宣言することができます。クラス修飾子について、詳細は後述します。

以降は標準 C++ 同様にクラスを記述します。フィールド(メンバ変数)やメソッド(メンバ関数)の書き方も同様です。通常の、管理されないアンマネージクラスとは様々な違いもありますが、基本的には参照クラスのオブジェクトの寿命が共通言語ランタイムによって管理されるという点を除いて、クラスとしての機能は標準 C++ と同じだと考えてかまいません。

例えば、参照クラス T を定義するには次のように記述します。

ref class T {  };

ref クラスのインスタンスは、標準 C++ の new 演算子では生成できません。new 演算しで参照クラスをインスタンス化しようとした場合はコンパイルエラーになります。参照クラスのインスタンスを生成する gcnew 演算子を使います。

gcnew 演算子
gcnew  (引数リスト)

基本的な使い方は new 演算子と同じです。gcnew に続いてネイティブ型または参照クラスなど、共通言語ランタイムによって管理されるインスタンスの型を指定します。引数リストにはコンストラクタのパラメータに渡す値を指定します。パラメータを受け取らないコンストラクタを起動する場合は省略することができます。

上の参照クラス T をインスタンス化する場合は、以下のように genew 演算子を用います。new 演算子を用いた場合はエラーになります。

new T();   //エラー
gcnew T(); //OK

マネージ型のコンストラクタや配列について、詳細は後述します。この場では明示的なコンストラクタを持たない単純な参照クラス型のインスタンスを生成してみましょう。

コード1
class Native {};
public ref class Managed {};

int main()
{
	System::Console::WriteLine(gcnew Managed());
	System::Console::WriteLine(gcnew int);
	System::Console::WriteLine(gcnew bool);
	//System::Console::WriteLine(new Managed()); //エラー
	//System::Console::WriteLine(gcnew Native); //エラー

	return 0;
}
実行結果
コード1 実行結果

コード1は、ref キーワードを用いて参照クラス Managed を宣言しています。ref キーワードで修飾されている参照クラス型は new 演算子でインスタンス化することはできません。参照クラスは gcnew 演算子を用いてインスタンス化しなければなりません。同様に非参照クラスは gcnew 演算子でインスタンス化することはできません。

gcnew int や gcnew bool で作成したオブジェクトは、従来の new int や new bool とは性質が異なり、実質的には .NET Framework の基本型である System::Int32 や System::Boolean 構造体のインスタンスを作成していると考えてください。単純に 32 ビットのメモリを割り当てるだけではなく、オブジェクトの初期化が行われ、適切なオブジェクトが返されています。

実行結果を見ると gcnew int を出力すると 0 が表示され、gcnew bool を出力すると Flase が表示されます。これは、WriteLine() メソッドが生成された int や bool オブジェクトが保持する値を表示しているためです。双方とも、正しく初期化されていることが確認されます。常に int オブジェクトの初期値は 0 であり、bool オブジェクトの初期値は false となります。

class と struct

標準 C++ では、クラスの宣言に class キーワード、struct キーワード、及び union キーワードを使うことができました。union キーワードによるクラスはメンバの記憶領域を共有するという点で class と struct キーワードによるクラスとは異色でしたが、class と struct キーワードを使ったクラス宣言の意味はほぼ同一でした。

C++/CLI による参照クラスの場合も class キーワード以外に struct キーワードを使ってクラスを宣言することができます。class と struct キーワードの違いは、アクセス修飾子を省略した場合の既定の可視性のみです。class キーワードで宣言されたクラスの場合、既定の可視性は private であると解釈されますが struct キーワードで宣言されたクラスは public であると解釈されます。

ただし、参照クラスは union キーワードで宣言することはできません。原則として、共用体の参照クラスを作ることはできません。近代のアプリケーション開発では、共用体のようなトリッキーな手法でプログラムを複雑にするようなコードを書くことは推奨できませんが、どうしても必要というならば属性と呼ばれる .NET Framework の機能を使って実現することができます。属性については後述します。

コード2
ref class A
{
	int field; //private
};

ref struct B
{
	int field; //public
};

int main()
{
	(gcnew A)->field = 10; //エラー
	(gcnew B)->field = 10; //OK

	return 0;
}
実行結果
コード2 実行結果

コード2で class キーワードと struct キーワードそれぞれで宣言したクラスの既定の可視性を調べることができます。class キーワードで宣言されている A クラスの既定の可視性は private ですが、struct キーワードで宣言された B クラスの既定の可視性は public となっています。

main() 関数で、それぞれのクラスのインスタンスを作成し、アクセス修飾子を省略して宣言をしている field フィールドにアクセスしています。A クラスのオブジェクト (gcnew A)->field は private であると解釈されているため、ここでコンパイルエラーが発生します。これに対して (gcnew B)->field は public であると解釈されるため外部からのアクセスに問題はありません。

既定の可視性を除けば class と struct の違いはありませんが、習慣的に標準 C++ の時代から struct キーワードは単純なデータ記憶用のクラスを作成するために使われました。こうした型のことを POD (Plain Old Data) と呼びます。C++/CLI において積極的に struct を使う理由はないので通常は class でよいと考えられますが、こうした過去の習慣から struct を使う開発者もいることでしょう。