Home > C++/CLI > ハンドルとポインタと参照

ハンドルとポインタと参照

  • Posted by: zakio
  • 2009年2月25日 21:56
  • C++/CLI

C++ に慣れ親しんだ私は、C++/CLI のハンドルを CLI heap 用のポインタという風に理解している。native heap は new で確保してポインタへ、CLI heap は gcnew で確保してハンドルへという単純な話だ。それだけならわざわざ取り上げる必要もないが、色々と調べているうちに混乱してきたので書きながら整理してみようと思う。

まずは単純な話から。


using namespace std;
using namespace System::Collections::Generic;

vector<int>* pVec = new vector<int>; // native heap を new で確保して pointer で受ける
List<int>^ hList = gcnew List<int>; // CLI heap を gcnew で確保して handle で受ける

// -> は同じ
pVec->push_back(1);
hList->Add(1);

// * も同じ
(*pVec).push_back(2);
(*hList).Add(2);

// delete も同じ
delete pVec;
delete hList;

vector に対応するものが List という点が気持ち悪いが、まぁこれはそういうもの。大事なのは new → gcnew, * → ^ という対応の部分だ。一旦領域を確保してしまえば、後は C++ と同じように扱える。

それでは参照はどうか。C++/CLI は CLI heap 用の参照としてトラッキング参照(追跡参照)というものを用意している。こちらは & の代わりに % を使えばよい。


vector<int>& rVec = *pVec;
List<int>% tList = *hList;

cout << "rVec = " << rVec[0] << ", " << rVec[1] << endl;
cout << "tList = " << tList[0] << ", " << tList[1] << endl;

ここまでは簡単だ。CLI heap を扱う場合は new の代わりに gcnew、* や & の代わりに ^ や % を使えば、C++ と同じようにポインタや参照を扱うことが出来る。

ところで、C++ では実体からポインタを得る場合にも & が用いられる。CLI heap 上の実体からハンドルを得る場合も、先ほどと同様に & を % に置き換えればよい。


vector<int>* pVec2 = &rVec; // C++ では & でポインタが得られる
List<int>^ hList2 = %tList; // これに相当する演算子もやはり %

cout << (pVec == pVec2) << endl; // pVec と pVec2 は同じものを指す
cout << (hList == hList2) << endl; // hList と hList2 も同じものを指す

これはこれで納得できるような気もするが、参照はがしの時は (*hList).Add(2); のようにハンドルに対して ^ ではなく * を使っていたではないか。それに合わせるなら実体からハンドルを得る場合も % ではなく & だと思うし、* → ^, & → % という対比を重視するならハンドルの参照はがしの方を * ではなく ^ にすべきだと思う。こういうズレが混乱を招くような気がするのだが、あまりそういった話を聞かないのは何故だろう。


vector<int>& rVec = *pVec;
List<int>% tList = *hList; // こうやって並べるとココの * が気になってくる
vector<int>* pVec2 = &rVec;
List<int>^ hList2 = %tList;

vector<int>& rVec = *pVec;
List<int>% tList = ^hList; // * に対応する ^ で参照を剥がすか、
vector<int>* pVec2 = &rVec;
List<int>^ hList2 = %tList;

vector<int>& rVec = *pVec;
List<int>% tList = *hList;
vector<int>* pVec2 = &rVec;
List<int>^ hList2 = &tList; // & でハンドルを得てくれればスッキリしたのに......

まぁ、文句を言っても仕方がないので、これはこういうもんだと思うしかない。

次に、こんな関数を用意してみた。


void Func1(vector<int>** ppVec) {
  *ppVec = new vector<int>(10);
}

void Func2(List<int>^^ hhList) {
  *hhList = gcnew List<int>(10);
}

Func1 はポインタそのものを書き換えるために、ポインタへのポインタを引数としている。Func2 は Func1 の * を ^ に置き換えたもの。* → ^ というルールで全てうまくいくなら Func2 も問題ないはずだが、残念ながらこれはコンパイルエラーとなってしまう。


error C3699: '^' : cannot use this indirection on type 'System::Collections::Generic::List ^'

C++/CLI の仕様書にはそれらしい記述が見当たらなかったので、これが言語的に禁止されているかどうかは不明。ただ、ハンドルのハンドルを許してしまうと、ハンドルのハンドルのハンドルのハンドルの......ハンドルも合法となり、ガベージコレクションの時に悲惨なことになりそうな気はする。理由はともかく、C3699 の説明には「^^ の代わりに ^% を使え」とあるので、コンパイルを通したければそれに従うしかない。^% は C++ 的には *& に相当するので、これは「ハンドルそのものを参照渡ししなさい」という意味だ。「そりゃぁそうなんだけど・・・・・・」と、モヤモヤしたまま書き直した以下のコードは(当然ながら)コンパイル可能。


void Func1(vector<int>*& pVec) {
  pVec = new vector<int>(10);
}

void Func2(List<int>^% hList) {
  hList = gcnew List<int>(10);
}

ハンドルやトラッキング参照にスタック構文が加わると話がもう少しややこしくなるのだが、長くなってきたので別の機会に。

まとめ

  • CLI heap は new ではなく gcnew で確保し、* ではなく ^(ハンドル)で受ける
  • CLI heap 用の参照は & ではなく %(トラッキング参照, 追跡参照)
  • ポインタもハンドルも参照はがしは * だが、トラッキング参照からハンドルを得る場合は & ではなく %
  • ^^(ハンドルのハンドル)は不可

参考文献

Comments:1

zakio Author Profile Page 2009年4月 6日 22:39

ハンドルやトラッキング参照にスタック構文が加わってややこしくなった話は「値型とボックス化」にまとめました。

コメントを表示する前に、このブログのオーナーによる承認が必要になることがあります。コメントが表示されない場合は、承認されるまでしばらくお待ちください。

Comment Form

Home > C++/CLI > ハンドルとポインタと参照

Search
Feeds

Return to page top