指向对象的句柄运算符 (^)(C++/CLI 和 C++/CX)
句柄声明符^
(发音为“hat”)将类型说明符修改为,指明当系统确定已声明的对象不再可访问时,应自动删除此对象。
访问声明的对象
使用句柄声明符声明的变量与指向对象的指针具有类似的行为。 但是,该变量指向整个对象,不能指向对象的某个成员,也不支持指针算法。 可使用间接寻扯运算符 (*
) 访问对象,使用箭头成员访问运算符 (->
) 访问对象的成员。
Windows 运行时
编译器使用 COM 引用计数机制来确定对象是否不再被使用,且能否被删除。 因为从 Windows 运行时接口派生的对象实际上是 COM 对象,所以这是可行的。 在创建或复制对象时,引用计数会递增;当对象设置为 null 或超出范围时,引用计数会递减。 如果引用计数归零,将立即自动删除对象。
句柄声明符的优点在于,在 COM 中,您必须以显式方式管理对象的引用计数,而这个过程单调乏味又容易出错。 也就是说,要递增或递减引用计数,必须调用对象的 AddRef() 和 Release() 方法。 不过,如果你使用句柄声明符来声明对象,编译器生成自动调整引用计数的代码。
若要详细了解如何实例化对象,请参阅 ref new。
要求
编译器选项:/ZW
公共语言运行时
系统使用 CLR 垃圾回收器机制来确定对象是否不再被使用,且能否被删除。 公共语言运行时会维护一个用来分配对象的堆,并在程序中使用托管引用(变量)来指示对象在堆上的位置。 当不再使用某个对象时,会释放它在堆上占用的内存。 垃圾回收器会定期压缩该堆,已更好地利用释放的内存。 压缩堆可能会移动堆上的对象,进而导致托管引用所引用的位置无效。 但是,垃圾回收器知道所有托管引用的位置,并会自动更新位置来指示对象在堆上的当前位置。
因为本机 C++ 指针 (*
) 和引用 (&
) 都是托管引用,所以垃圾回收器不能更新它们指向的地址。 若要解决此问题,请使用句柄声明符指定一个变量,垃圾回收器能够知道这个变量的状态并会自动进行更新。
有关的详细信息,请参阅如何:使用本机类型声明句柄。
示例
此示例演示如何在托管堆上创建引用类型的实例。 此示例还演示,您可以使用一个句柄初始化另一个句柄,使两个引用都指向垃圾回收托管堆上的同一对象。 请注意,将 nullptr 赋给一个句柄不会将对象标记为可供垃圾回收。
// mcppv2_handle.cpp
// compile with: /clr
ref class MyClass {
public:
MyClass() : i(){}
int i;
void Test() {
i++;
System::Console::WriteLine(i);
}
};
int main() {
MyClass ^ p_MyClass = gcnew MyClass;
p_MyClass->Test();
MyClass ^ p_MyClass2;
p_MyClass2 = p_MyClass;
p_MyClass = nullptr;
p_MyClass2->Test();
}
1
2
以下示例演示如何声明一个句柄,指向托管堆上的一个对象,而对象的类型是装箱值类型。 示例还演示如何从装箱对象获取值类型。
// mcppv2_handle_2.cpp
// compile with: /clr
using namespace System;
void Test(Object^ o) {
Int32^ i = dynamic_cast<Int32^>(o);
if(i)
Console::WriteLine(i);
else
Console::WriteLine("Not a boxed int");
}
int main() {
String^ str = "test";
Test(str);
int n = 100;
Test(n);
}
Not a boxed int
100
此示例展示了使用 void*
指针指向 Object^
替换的任意对象的常见 C++ 惯例,其中可以包含指向任意引用类的句柄。 它还演示可将所有类型(如数组和委托)都转换为对象句柄。
// mcppv2_handle_3.cpp
// compile with: /clr
using namespace System;
using namespace System::Collections;
public delegate void MyDel();
ref class MyClass {
public:
void Test() {}
};
void Test(Object ^ x) {
Console::WriteLine("Type is {0}", x->GetType());
}
int main() {
// handle to Object can hold any ref type
Object ^ h_MyClass = gcnew MyClass;
ArrayList ^ arr = gcnew ArrayList();
arr->Add(gcnew MyClass);
h_MyClass = dynamic_cast<MyClass ^>(arr[0]);
Test(arr);
Int32 ^ bi = 1;
Test(bi);
MyClass ^ h_MyClass2 = gcnew MyClass;
MyDel^ DelInst = gcnew MyDel(h_MyClass2, &MyClass::Test);
Test(DelInst);
}
Type is System.Collections.ArrayList
Type is System.Int32
Type is MyDel
此示例演示可以对句柄取消引用,并通过取消引用的句柄访问成员。
// mcppv2_handle_4.cpp
// compile with: /clr
using namespace System;
value struct DataCollection {
private:
int Size;
array<String^>^ x;
public:
DataCollection(int i) : Size(i) {
x = gcnew array<String^>(Size);
for (int i = 0 ; i < Size ; i++)
x[i] = i.ToString();
}
void f(int Item) {
if (Item >= Size)
{
System::Console::WriteLine("Cannot access array element {0}, size is {1}", Item, Size);
return;
}
else
System::Console::WriteLine("Array value: {0}", x[Item]);
}
};
void f(DataCollection y, int Item) {
y.f(Item);
}
int main() {
DataCollection ^ a = gcnew DataCollection(10);
f(*a, 7); // dereference a handle, return handle's object
(*a).f(11); // access member via dereferenced handle
}
Array value: 7
Cannot access array element 11, size is 10
此示例演示不能将本机引用 (&
) 绑定到托管类型的 int
成员,因为 int
可能存储在垃圾回收堆中,但本机引用不能跟踪托管堆中的对象移动。 解决方法是使用局部变量,或将 &
更改为 %
,使它成为跟踪引用。
// mcppv2_handle_5.cpp
// compile with: /clr
ref struct A {
void Test(unsigned int &){}
void Test2(unsigned int %){}
unsigned int i;
};
int main() {
A a;
a.i = 9;
a.Test(a.i); // C2664
a.Test2(a.i); // OK
unsigned int j = 0;
a.Test(j); // OK
}
要求
编译器选项:/clr