VC.NET ~ マネージ・ヒープとgcnew、reference type、ハンドル、追跡参照

前置き

予備知識なしにC++/CLIのコードを初めて見た人が、まず度肝を抜かれるのが、^ の用法でしょう。XORではありません。ハンドルと呼ばれる新表記で、マネージ・ヒープ上のオブジェクトを指すポインタのようなものです。ここでは、マネージ・ヒープに関するC++/CLIの機能を紹介します。

本文

マネージ・ヒープ

マネージ・ヒープは、CLI(CLR)が提供するヒープです。GC(ガベッジ・コレクション)機能を備えているのが大きな特徴です。これと区別するため、C/C++の標準ライブラリが提供する従来のヒープをネイティブ・ヒープと呼んだりします。

gcnewとハンドル

gcnewとハンドルは、ネイティブ・ヒープのnewとポインタ(*)に対応します。つまり、マネージ・ヒープ上にオブジェクトを生成するのがgcnewで、生成したオブジェクトを指すのがハンドルです。ハンドルは、ハット記号(^)で表記します。紛らわしい話しですが、ハンドルは、Win32のHANDLE型やウィンドウハンドルとは別の物です。

ref class Foo {
public:
  Foo(int i) { ii = i; }
  void Print() { Console::WriteLine(ii); }
private:
  int ii;
};

Foo^ foo()
{
  Foo f(3);                  // マネージ・ヒープ
  Foo^ ff = gcnew Foo(100);  // マネージ・ヒープ
  f.Print();                 // 3
  ff->Print();               // 100
  return ff;
}

ffがハンドルです。Fooクラスはreference typeなので、fもffも、マネージ・ヒープに置かれます。ただしfは、関数foo()がreturnする時点でディスポーズされます(reference typやディスポーズについては、この記事この記事を参照)。ハンドルが指すオブジェクトのメンバにアクセスするには、ポインタのときと同様、->を使います。ハンドルをdeleteすることは可能ですが、deleteしてもマネージ・ヒープが解放されるわけではありません(ディスポーズされるだけ)。

value typeをgcnewすることも可能です。この場合は、Box化されたうえでマネージ・ヒープに置かれます。

value struct Bar {
  int ii;
  void Print() { Console::WriteLine(ii); }
};

void bar()
{
  Bar b;                     // スタック
  Bar^ bb = gcnew Bar();     // Box化されてマネージ・ヒープ
  bb->ii = 100;
  bb->Print();               // 100
}

追跡参照

標準C++には、参照(&)と呼ばれる、ポインタの親戚のような機能があります。これに対し、C++/CLIには、追跡参照(Tracking Reference)と呼ばれる機能があります。

int i = 100;
int* pi = &i;                // ポインタを得るには、&を使う
Console::WriteLine(*pi);     // 100
int& ri = i;
ri = 50;                     // 参照を変更すると、参照元(i)が変わる
Console::WriteLine(i);       // 50

int j = 100;
int^ hj = j;                 // Box化されマネージ・ヒープ
hj = 50;
Console::WriteLine(j);       // 100
int% trj = j;
trj = 50;
Console::WriteLine(j);       // 50

Foo f(100);
Foo^ hf = %f;                // ハンドルを得るには、%を使う
Foo% trf = f;
trf.Print();                 // 100
Foo^% trhf = hf;
trhf = gcnew Foo(50);        // 追跡参照を変更すると、参照元(hf)が変わる
hf->Print();                 // 50

array<String^>^ hellos = gcnew array<String^>(10);
for each (String^ s in hellos) s = "Hello";
Console::WriteLine(hellos[0]);               // hellos[0]はnullptr
for each (String^% s in hellos) s = "Hello";
Console::WriteLine(hellos[0]);               // Hello

nullptr

ポインタの0やNULLに相当するのは、ハンドルではnullptrです。nullptrは、ハンドルが何も指してない状態を意味します。ハンドルにnullptrを代入するのは、ハンドルを何も指してない状態に戻すことを意味します。ハンドルとnullptrを比較するのは、ハンドルが何も指してないか(何かを指しているか)を調べることを意味します。ハンドルを条件式の中で単独で使うと、何も指してなければfalse、何かを指していればtrueと扱われます。

一方、参照に未初期化状態が許されなかったのと同様に、追跡参照にも未初期化状態は許されません。

Foo^ f;
if ( f == nullptr ) Console::WriteLine("null");  // null
f = gcnew Foo(100);
if ( f ) Console::WriteLine("not null");         // not null
f = nullptr;

Foo% trf;                                        // error C2530
Last modified:2012/03/06 22:18:22
Keyword(s):
References:[.NETアプリ開発] [VC.NET ~ C++/CLIマイナスC++]
This page is frozen.