在C ++ / CLI中跟踪引用

时间:2010-08-01 00:40:37

标签: c++-cli

有人可以向我解释下面的代码片段吗?

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类型没有区别,因为两者都可以被处理程序指向;(

1 个答案:

答案 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名称,也无法执行装箱