在托管 C++ 中,请告诉我使用 delete 操作符销毁托管对象是否安全?
是的,在托管 C++ 中,你可以删除( delete )托管对象,只要你理解删除只不过是调用对象的析构函数,但析构函数必须显示定义。调用 delete 不会释放对象的存储区。只有垃圾收集器才行。Figure 1 展示了一个简单的程序,该程序定义了一个带析构函数的托管类,当它运行的时候会显示一条信息。TESTDTOR 分配两个 ManagedClass 实例。它显式删除第一个实例,但第二个则不然。如果运行 TESTDTOR,你会得到象下面这样的结果:
Begin main
ManagedClass(04A712D4)::ctor
ManagedClass(04A712D4)::dtor
ManagedClass(04A712E0)::ctor
End main
ManagedClass(04A712E0)::dtor
它说明了当 delete 语句执行时,第一个对象的析构函数立即执行;而第二个对象(at 04A712E0)则没有被销毁,直到控制离开 main 并且系统终止代码调用垃圾收集器释放逗留对象。
Figure 2 Testdtor 的精彩输出
不管什么时候,如果你不能确定 .NET 环境中发生了什么,你总是可以编写一些代码,编译它并检查微软中间语言(MSIL)产生的东西。正如 Figure 2 所展示的,定义析构函数导致编译器产生两个方法:一个是 Finalize 方法,它包含你的实现(这里是调用 printf),一个是 __dtor 方法,它调用 System.GC::SuppressFinalize,然后再调用 Finalize。当你删除对象时,编译器产生一个对此 __dtor 方法的掉用。如果你用 /FAs 编译 TESTDTOR 来产生有源码的程序集清单,你将看到 delete 语句以如下的方式编译:
; delete pmc;ldloc.0 ; _pmc$call ??1ManagedClass@@$$FQ$AAM@XZ
老练的 C++ 程序员可能会弄不明白,如果调用 delete 都无法释放对象,那调用它有干什么?好问题。调用 delete 的唯一理由是收回任何你的类所使用的非托管资源。例如,如果你的对象打开数个文件或创建了数据库连接,你可以写一个关闭其资源的析构函数,然后在用完该对象时使用 delete 释放它。在托管类中释放资源的一个更好的方法是通过实现 Dispose 模式,IDisposable——如果你在写托管 C++ 代码——由 auto_dispose 来调用它。(更多的信息参见 Tomas Restrepo 在 MSDN 杂志 2002 二月刊上的文章:“Tips and Tricks to Bolster Your Managed C++ Code in Visual Studio .NET”)。
如果你实现 dispose 模式,其他的 .NET 使用者也可以使用它。如果你自己在析构函数中进行清理,其它语言便没有办法显式调用你的清理代码。因为在 C# 和 Visual Basic 中没有 delete 操作符。
所以结果是你能调用 delete 来触发你的析构函数,但是将清理代码放在析构函数中可能不是一个好主意。最好是实现 IDisposable,这样所有人都能使用。注意,在 Visual C++ 2005 中,这个行为有所改变。更多信息参见 Andy Rich 对这个问题的讨论:“Deterministic Finalization IV - Benefits, part II”,以及当前的 C++/CLI 语言规范标准:“C++/CLI Language Specification Standard”
我有一个返回链表的非托管函数,其中有 char* 字符串:
struct blah { int a, b; char *a, *b; struct blah *next;};struct blah *getmystruct();
因为 getmystruct() 分配内存,当用完之后,我需要调用 freemystruct(struct blah *b)。我尝试做一个包装器,用它来将之转换成托管类型的集合,但我不知道当需要释放所有这些指针的时候,该如何来处理。你能否赐教?
为什么,的确。你不能用 dllimport 语句将你的本地链表转换成托管类型集合。interop 服务固然不错,但处理此问题也不是那么好!你需要编写一个包装器,显式地将你的链表转换为托管集合,象 ArrayList。我写了一个带有三个模块的程序,ListWrap,它示范了具体做法。第一个模块,ListLib.cpp,实现一个本地 C++ 库(DLL),其中有两个函数,AllocateList 和 FreeList。分别用来分配和释放本地 C++ 结构链表。它们模仿你程序中的 getmystruct 和 freemystruct 函数。第二个模块是一个托管 C++ 文件,ListWrap.cpp,它实现托管类 ManagedNode,该类包装本地 C++ 实现(参见 Figure 3)。第三个模块是 C# 测试程序,它调用包装器来展示它如何工作。详情请下载源代码。
ListLib.cpp 实现两个本地函数,AllocateList 和 FreeList,这两个函数用来分配和释放 NativeNode 结构链表:
// from ListLib.h
struct NativeNode {
int a, b;
TCHAR *str;
struct NativeNode *next;
};
ListWrap.cpp 中的包装器类 ManagedNode 模仿用 NativeNode 的定义,只是有两个小差别:本地 char* 被用托管的 String 代替,此外它没有 next 指针,因为我将用 ArrayList 实现链表结构。代码如下:
// managed equivalent of NativeNode
public __gc class ManagedNode {
public:
int a, b;
String* str;
};
有了 ManagedNode 的定义,下一步是编写代码将 NativeNodes 转换到 ManagedNodes。但在开始之前,先停下来考虑一下转换函数应该是什么样子,他应该有什么样的参数,返回什么值。一种方法是编写一个函数,参数是本地 NativeNodes 链表并返回托管的 ManagedNodes 链表,在这个过程中可能销毁本地链表。.NET 客户端应用程序将直接调用 ListLib DLL (或你的 getmystruct )以获取本地链表,将它作为 IntPtr 类型。然后,将这个 IntPtr 传递给转换函数,象下面这样:
// call DLL directly through interop
IntPtr nativeList = AllocateList(7);
// call wrapper to convert
ArrayList amanagedList = ListWrap.Convert(nativeList);
大多数情况下,客户端将负责调用该 DLL 来释放本地链表,或者 Convert 函数自动完成。
一种不同的方法是通过在某个包装器中包装分配链表的本地函数 AllocateList 来完全隐藏这个 DLL,转换并在作为 ArrayList 返回托管链表之前释放原来的本地链表。哪种方法更好的呢?第一种策略的优点是你只需要编写单一的转换函数,它便可以在任何有本地链表的地方使用。第二个方法需要对每个创建链表的函数进行包装。如果有多个创建链表的函数,则工作量稍大一些。但是其优点是它向 .NET 客户端完全隐藏了所有的本地处理逻辑和细节。客户端不再需要去处理 IntPtrs 或甚至是导入此 DLL,因为 ListWrap 隐藏了一切。这是我将要采用的方法,同时也是我鼓励你在自己的程序中使用的方法。尽管对库进行完全的包装需要更多的努力,但是结果却更加可靠和彻底的封装。
有了 ManagedNode,剩下的事情便是包装 AllocateList。这个过程非常简单直白。首先,调用 AllocateList 分配本地链表,然后创建一个空的 ArrayList,接着将所有 NativeNodes 拷贝到 ManagedNodes 并将它们添加到托管链表中,最有离开时删除它们。Figure 3 展示了所有的细节。托管 C++ 的优美之处在于即便是在处理混合对象时,所有的代码看起来都很简朴优雅。将本地 char* 拷贝到托管 String 用一个赋值即可,就像下面这行代码:
mn->str = nn->str; // String = char*: it just works!
不需要调用转换函数;编译器知道该怎么做。离开 CreateList 时删除本地节点。这样做比在末尾删除它们存储更有效。
以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索c++
, 对象
, 函数
, c库转换c#可用库
, 本地
, 托管
, class struct 链表
, c++类 链表
, 文件操作c++链表
, c++链表
, c++ 数据结构 链表 c
, 一个
, C++中的delete
C++信息隐藏类型
托管对象、非托管对象、托管dll包装、托管模式无法删除、处于托管模式无法删除,以便于您获取更多的相关知识。