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 = ???
更重要的是:我认为值类型和引用类型之间没有区别,因为两者都可以由处理程序指向;(
Can someone please explain me the following code snippet?
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 ?
And my second question is: is there any reason to have something like that?:
ref struct RefStruct {
int x;
};
RefStruct% ref = *gcnew RefStruct;
// rather than:
// RefStruct^ ref = gcnew RefStruct;
// can I retrieve my handle from ref?
// RefStruct^ myref = ???
What is more: I see no difference between value type and ref type, since both can be pointed by handler ;(
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
请记住,C++/CLI 的主要用途是开发类库,供其他 .NET 语言构建的 GUI/Web 服务使用。因此,C++/CLI 必须支持引用类型和值类型,就像其他 .NET 语言一样。
此外,C# 也可以具有值类型的
ref
参数,这并不是 C++/CLI 独有的,并且它不会以任何方式使值类型等同于引用类型。要回答代码注释中的问题:
是的,SetValueTwo 按值获取其参数,因此会创建一个副本。
不正确。值类型可以有析构函数。值类型不能有终结器。由于此特定值类型有一个简单的析构函数,因此 C++/CLI 编译器不会使其实现 IDisposable。无论如何,如果参数是 IDisposable 值类型,C++/CLI 编译器将确保在变量超出范围时调用 Dispose,就像局部变量的堆栈语义一样。这包括异常终止(引发异常),并允许托管类型与 RAII 一起使用。
和
都是
允许的,并将一个装箱值类型实例放在托管堆上(这根本不是堆,而是一个 FIFO 队列,但是 Microsoft 选择将其称为堆,就像用于动态分配的本机内存区域一样)。
与 C# 不同,C++/CLI 可以保留装箱对象的类型句柄。
如果跟踪引用是对堆栈上的值类型实例或嵌入到另一个对象中的值类型实例,则必须在形成引用的过程中对值类型内容进行装箱。
跟踪引用也可以与引用类型一起使用,并且获取句柄的语法是相同的:
我似乎永远无法完全记住的一件事是,与本机 C++ 相比,该语法并不是 100% 一致。
声明引用:
声明指针:
形成指针:
请注意,一元取址运算符与标准 C++ 和 C++/CLI 中的引用表示法相同。这似乎与 跟踪引用不能用作一元取地址运算符 ( MSDN) 我稍后会再讲。
首先,不一致之处:
从指针形成引用:
请注意,一元取消引用运算符始终为
*
。它与仅在本机世界中的指针表示法相同,这与地址完全相反,如上所述,地址表示法始终与引用表示法相同的符号。可编译的示例:
现在,关于一元地址:
首先,MSDN 文章是错误的:
正确的说法是:
%
是用于创建跟踪句柄的取址运算符。但是它的使用受到以下限制:跟踪句柄必须指向托管堆上的对象。引用类型始终存在于托管堆上,因此没有问题。但是,值类型和本机类型可能位于堆栈上(对于局部变量)或嵌入到另一个对象中(值类型的成员变量)。尝试形成跟踪句柄将形成变量装箱副本的句柄:该句柄不链接到原始变量。由于装箱过程需要本机类型不存在的元数据,因此永远不可能拥有本机类型实例的跟踪句柄。
示例代码:
如果
System::Int32
不是值类型,那么我不知道是什么类型。让我们尝试一下 System::DateTime,它是一种非原始值类型:这有效!
作为进一步不幸的限制,具有双重身份的原始类型(例如本机
int
和托管值类型System::Int32
)无法正确处理,%< /code>(表单跟踪引用)操作符无法执行装箱,即使给出了类型的 .NET 名称。
Remember that the primary use of C++/CLI is for developing class libraries for consumption by GUIs / web services built in other .NET languages. So C++/CLI has to support both reference and value types because other .NET languages do.
Furthermore, C# can have
ref
parameters that are value typed as well, this isn't unique to C++/CLI and it doesn't in any way make value types equivalent to reference types.To answer the questions in your code comments:
Yes, SetValueTwo takes its parameter by value, so a copy is made.
Incorrect. Value types can have destructors. Value types cannot have finalizers. Since this particular value type has a trivial destructor, the C++/CLI compiler will not cause it to implement IDisposable. In any case, if a parameter is an IDisposable value type, the C++/CLI compiler will ensure that Dispose is called when the variable goes out of scope, just like stack semantics for local variables. This includes abnormal termination (thrown exception), and allows managed types to be used with RAII.
Both
and
are allowed, and put a boxed value type instance on the managed heap (which isn't a heap at all, but a FIFO queue, however Microsoft chooses to call it a heap like the native memory area for dynamic allocation).
Unlike C#, C++/CLI can keep typed handles to boxed objects.
If a tracking reference is to a value type instance on the stack or embedded in another object, then the value type content has to be boxed in the process of formed the reference.
Tracking references can also be used with reference types, and the syntax to obtain a handle is the same:
One thing that I can never seem to remember completely is that the syntax isn't 100% consistent compared to native C++.
Declare a reference:
Declare a pointer:
Form a pointer:
Note that the unary address-of operator is the same as the reference notation in both standard C++ and C++/CLI. This seems to contradict a tracking reference cannot be used as a unary take-address operator (MSDN) which I'll get back to in a second.
First though, the inconsistency:
Form a reference from a pointer:
Note that the unary dereference operator is always
*
. It is the same as the pointer notation only in the native world, which is completely opposite of address-of which, as mentioned above, are always the same symbol as the reference notation.Compilable example:
Now, about unary address-of:
First, the MSDN article is wrong when it says:
The correct statement is:
%
is the address-of operator for creation of a tracking handle. However its use is limited as follows:A tracking handle must point to an object on the managed heap. Reference types always exist on the managed heap so there is no problem. However, value types and native types may be on the stack (for local variables) or embedded within another object (member variables of value type). Attempts to form a tracking handle will form a handle to a boxed copy of the variable: the handle is not linked to the original variable. As a consequence of the boxing process, which requires metadata which does not exist for native types, it is never possible to have a tracking handle to an instance of a native type.
Example code:
If
System::Int32
isn't a value type then I don't know what is. Let's trySystem::DateTime
which is a non-primitive value type:This works!
As a further unfortunate restriction, primitive types which have dual identity (e.g. native
int
and managed value typeSystem::Int32
) are not handled correctly, the%
(form tracking reference) operator cannot perform boxing even when the .NET name for the type is given.