有人可以向我解释以下代码段吗?
value struct ValueStruct {
int x;
};
void SetValueOne(ValueStruct% ref) {
ref.x = 1;
}
void SetValueTwo(ValueStruct ref) {
ref.x = 2;
}
void SetValueThree(ValueStruct^ ref) {
ref->x = 3;
}
ValueStruct^ first = gcnew ValueStruct;
first->x = 0;
SetValueOne(*first);
ValueStruct second;
second.x = 0;
SetValueTwo(second); // am I creating a copy or what? is this copy Disposable even though value types don't have destructors?
ValueStruct^ third = gcnew ValueStruct;
third->x = 0;
SetValueThree(third); // same as the first ?
我的第二个问题是:有什么理由要这样做吗?:
ref struct RefStruct {
int x;
};
RefStruct% ref = *gcnew RefStruct;
// rather than:
// RefStruct^ ref = gcnew RefStruct;
// can I retrieve my handle from ref?
// RefStruct^ myref = ???
更重要的是:我看不到值类型和ref类型之间的区别,因为两者都可以由处理程序;(
最佳答案
请记住,C++/CLI的主要用途是开发类库,以供使用其他.NET语言构建的GUI/Web服务使用。因此C++/CLI必须同时支持引用和值类型,因为其他.NET语言也支持。
此外,C#也可以具有值类型的ref
参数,这不是C++/CLI所独有的,它也不以任何方式使值类型等效于引用类型。
要回答代码注释中的问题:
am I creating a copy or what?
是的,SetValueTwo通过值获取其参数,因此进行了复制。
is this copy Disposable even though value types don't have destructors?
不正确值类型可以具有析构函数。值类型不能具有终结器。由于此特定值类型具有琐碎的析构函数,因此C++/CLI编译器不会使它实现IDisposable。无论如何,如果参数是IDisposable值类型,则C++/CLI编译器将确保在变量超出范围时调用Dispose,就像局部变量的堆栈语义一样。这包括异常终止(引发异常),并允许将托管类型与RAII一起使用。
两个都
ValueStruct% ref = *gcnew ValueStruct;
和
ValueStruct^ ref = gcnew ValueStruct;
允许,然后将一个装箱的值类型实例放在托管堆上(它根本不是一个堆,而是一个FIFO队列,但是Microsoft选择将其称为堆,例如用于动态分配的 native 内存区域)。
与C#不同,C++/CLI可以保留盒装对象的类型化句柄。
如果跟踪引用是针对堆栈上的值类型实例或嵌入到另一个对象中的值类型实例,则必须在形成引用的过程中对值类型内容进行装箱。
跟踪引用也可以与引用类型一起使用,并且获取句柄的语法是相同的:
RefClass^ newinst = gcnew RefClass();
RefClass% reftoinst = *newinst;
RefClass^% reftohandle = newinst;
RefClass stacksem;
RefClass^ ssh = %stacksem;
我似乎永远无法完全记住的一件事是,与 native C++相比,语法不是100%一致的。
声明引用:
int& ri = i; // native
DateTime% dtr = dt; // managed tracking reference
声明一个指针:
int* pi; // native
Stream^ sh; // tracking handle
形成一个指针:
int* pi = &ri; // address-of native object
DateTime^ dth = %dtr; // address-of managed object
请注意,一元地址运算符与标准C++和C++/CLI中的引用符号相同。这似乎与a tracking reference cannot be used as a unary take-address operator (MSDN)矛盾,我将在稍后再讲。
首先,不一致之处:
从指针形成引用:
int& iref = *pi;
DateTime% dtref = *dth;
请注意,一元解引用运算符始终为
*
。它与仅在 native 环境中的指针表示法相同,这与地址完全相反-如上所述,其地址始终与引用表示法相同。编译示例:
DateTime^ dth = gcnew DateTime();
DateTime% dtr = *dth;
DateTime dt = DateTime::Now;
DateTime^ dtbox = %dt;
FileInfo fi("temp.txt");
// FileInfo^ fih = &fi; causes error C3072
FileInfo^ fih = %fi;
现在,关于一元地址:
首先,MSDN文章在显示以下内容时是错误的:
The following sample shows that a tracking reference cannot be used as a unary take-address operator.
正确的声明是:
%
是用于创建跟踪句柄的地址运算符。但是,其使用受到如下限制:跟踪句柄必须指向托管堆上的对象。引用类型始终存在于托管堆上,因此没有问题。但是,值类型和 native 类型可以在堆栈上(用于局部变量),也可以嵌入在另一个对象内(值类型的成员变量)。尝试形成跟踪句柄将形成该变量的盒装副本的句柄:该句柄未链接到原始变量。由于装箱过程需要 native 类型不存在的元数据,因此永远不可能拥有对 native 类型实例的跟踪句柄。
示例代码:
int i = 5;
// int^ ih = %i; causes error C3071
System::Int32 si = 5;
// System::Int32^ sih = %si; causes error C3071
// error C3071: operator '%' can only be applied to an instance
// of a ref class or a value-type
如果
System::Int32
不是值类型,那么我不知道它是什么。让我们尝试System::DateTime
,它是一个非原始值类型:DateTime dt = DateTime::Now;
DateTime^ dtbox = %dt;
这行得通!
另一个不幸的限制是,具有双重标识的原始类型(例如 native
int
和托管值类型System::Int32
)未正确处理,即使给定了类型的.NET名称,%
(表单跟踪引用)运算符也无法执行装箱操作。
关于c++-cli - C++/CLI中的跟踪引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3380315/