有人可以向我解释下面的代码片段吗?
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类型没有区别,因为两者都可以被处理程序指向;(
答案 0 :(得分:24)
请记住,C ++ / CLI的主要用途是开发类库,供其他.NET语言构建的GUI / Web服务使用。所以C ++ / CLI必须同时支持引用和值类型,因为其他.NET语言都可以。
此外,C#也可以有ref
个值类型的参数,这不是C ++ / CLI独有的,它不会以任何方式使值类型等同于引用类型。
回答代码评论中的问题:
我是在制作副本还是什么?
是的,SetValueTwo按值获取其参数,因此会复制。
这个副本是否为Disposable,即使值类型没有析构函数?
不正确的。值类型可以有析构函数。值类型不能有终结器。由于此特定值类型具有普通的析构函数,因此C ++ / CLI编译器不会使其实现IDisposable。在任何情况下,如果参数是IDisposable值类型,C ++ / CLI编译器将确保在变量超出范围时调用Dispose,就像局部变量的堆栈语义一样。这包括异常终止(抛出异常),并允许托管类型与RAII一起使用。
两个
ValueStruct% ref = *gcnew ValueStruct;
和
ValueStruct^ ref = gcnew ValueStruct;
是允许的,并且在托管堆上放置一个盒装值类型实例(根本不是堆,而是FIFO队列,但是Microsoft选择将其称为堆,就像本机内存区域一样用于动态分配)。
与C#不同,C ++ / CLI可以将类型句柄保留为盒装对象。
如果跟踪引用是堆栈上的值类型实例或嵌入到另一个对象中,则必须在形成引用的过程中将值类型内容装箱。
跟踪引用也可以与引用类型一起使用,获取句柄的语法是相同的:
RefClass^ newinst = gcnew RefClass();
RefClass% reftoinst = *newinst;
RefClass^% reftohandle = newinst;
RefClass stacksem;
RefClass^ ssh = %stacksem;
我似乎永远无法完全记住的一件事是,与原生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
请注意,一元address-of运算符与标准C ++和C ++ / CLI中的引用表示法相同。这似乎与a tracking reference cannot be used as a unary take-address operator (MSDN)相矛盾,我将在一秒内回复。
首先,不一致:
从指针形成引用:
int& iref = *pi;
DateTime% dtref = *dth;
请注意,一元解除引用运算符始终为*
。它与仅在本地世界中的指针符号相同,它与地址完全相反 - 如上所述,其总是与引用符号相同的符号。
可编辑的例子:
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文章说错了:
以下示例显示跟踪参考不能用作一元接收地址运算符。
正确的陈述是:
%
是用于创建跟踪句柄的地址运算符。但是它的使用受到如下限制:
跟踪句柄必须指向托管堆上的对象。托管堆上始终存在引用类型,因此没有问题。但是,值类型和本机类型可能位于堆栈上(对于局部变量)或嵌入在另一个对象(值类型的成员变量)中。尝试形成跟踪句柄将形成变量的盒装副本的句柄:句柄未链接到原始变量。由于装箱过程需要原生类型不存在的元数据,因此永远不可能拥有本机类型实例的跟踪句柄。
示例代码:
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;
这有效!
作为一个进一步的不幸限制,具有双重身份的原始类型(例如原生int
和托管值类型System::Int32
)未得到正确处理,%
(表单跟踪引用)运算符即使给出了类型的.NET名称,也无法执行装箱。