我与C ++ / CLI玩耍,使用MSDN文档和ECMA标准和Visual C ++ 2010年快递什么我吃惊的是,从C ++以下出发:
对于裁判班,无论是终结和析构函数必须写成这样他们就可以被执行多次,并在尚未完全构造的对象。
我编造了一个小例子:
#include <iostream>
ref struct Foo
{
Foo() { std::wcout << L"Foo()\n"; }
~Foo() { std::wcout << L"~Foo()\n"; this->!Foo(); }
!Foo() { std::wcout << L"!Foo()\n"; }
};
int main()
{
Foo ^ r;
{
Foo x;
r = %x;
} // #1
delete r; // #2
}
在该块的末#1
,自动变量x
模具,和析构函数被调用(这反过来调用终结明确地,由于是通常的成语)。 这是一切优秀和良好。 但后来我通过基准再次删除对象r
输出是这样的:
Foo()
~Foo()
!Foo()
~Foo()
!Foo()
问题:
它是未定义的行为,或者是完全可以接受,调用delete r
线#2
?
如果我们以删除线#2
,它的问题是r
仍然为(在C ++感)不再存在的对象跟踪处理? 它是一个“晃来晃去处理”? 难道它的引用计数意味着将有企图双缺失?
我知道,没有一个实际的双重缺失,作为输出变成这样:
Foo() ~Foo() !Foo()
但是,我不知道这是否是一个快乐的事故或保证是良好定义的行为。
在其其他情况下可以被管理对象的析构函数被调用一次以上?
难道是OK插入x.~Foo();
紧接之前或之后r = %x;
?
换句话说,你的管理对象“长生不老”,可以同时拥有自己的析构函数及其终结叫了一遍又一遍?
为了响应@汉斯对一个不平凡的类的需求,你也可以考虑这个版本(与析构函数和终结作出符合多个呼叫要求):
ref struct Foo
{
Foo()
: p(new int[10])
, a(gcnew cli::array<int>(10))
{
std::wcout << L"Foo()\n";
}
~Foo()
{
delete a;
a = nullptr;
std::wcout << L"~Foo()\n";
this->!Foo();
}
!Foo()
{
delete [] p;
p = nullptr;
std::wcout << L"!Foo()\n";
}
private:
int * p;
cli::array<int> ^ a;
};
我只是试图解决您带来了,为了这些问题:
对于裁判班,无论是终结和析构函数必须写成这样他们就可以被执行多次,并在尚未完全构造的对象。
析构函数~Foo()
简单地自动生成两个方法中,所述的IDisposable的实现:: Dispose()方法以及它实现了一次性图案的保护的Foo ::的Dispose(布尔)方法。 这些是普通的方法,因此可以被调用多次。 它允许在C ++ / CLI直接调用终结, this->!Foo()
和一般做,就像你一样。 垃圾收集器永远只能调用终结一次,它使内部跟踪不论这是否已完成。 鉴于主叫终结直接被允许,并且调用Dispose()多次被允许,因此可以运行终结代码一次以上。 这是专门针对C ++ / CLI,其他托管语言不答应。 你可以很容易地阻止它,一个nullptr检查通常能够完成任务。
它是未定义的行为,或者是完全可以接受的,叫上线#2删除R'
它不是UB和完全可以接受。 该delete
操作员只需调用了IDisposable :: Dispose()方法,从而运行您的析构函数。 你做什么里面,很典型的调用非托管类的析构函数,可能调用UB。
如果我们以删除线#2,它的问题是R还是一个跟踪手柄
号调用析构函数是没有强制执行的好方法完全是可选的。 一切正常,终结最终将始终运行。 在给定的例子,当CLR运行终结器线程最后一次关机前会发生。 唯一的副作用就是程序运行“重”,抱着一种资源超过所需的时间。
在其其他情况下可以被管理对象的析构函数被调用一次以上?
这是很常见的,一个过分热心的C#程序员很可能打电话给你的Dispose()方法不止一次。 同时提供一个关闭和Dispose方法类是在框架中相当普遍。 有一定的模式,其中它几乎是不可避免的,在另一个类假定有一个对象的所有权的情况下。 标准的例子是该位的C#代码:
using (var fs = new FileStream(...))
using (var sw = new StreamWriter(fs)) {
// Write file...
}
该StreamWriter对象将它的基本流的所有权,并在最后的大括号调用其Dispose()方法。 FileStream对象上的using语句调用Dispose()第二次。 写这个代码,以便不会发生这种情况,并仍然提供担保的例外是太困难了。 指定的Dispose()可以被称为不止一次地解决了这个问题。
难道是OK插入X〜美孚()。 紧接之前或之后的R =%×;?
没关系。 结果不太可能是愉快的,一个NullReferenceException将是最有可能的结果。 这是东西,你应该测试,抛出的ObjectDisposedException给程序员更好的诊断。 所有标准.NET Framework类这样做。
换句话说,做管理对象“长生不老”
不,垃圾收集声明的对象死亡,并收集它,当它不能找到对象的任何引用了。 这是一个故障安全方式的内存管理,也没有办法意外引用删除的对象。 因为这样做需要一个参考,一个是,GC总会看到。 像循环引用常见的内存管理问题也不是问题。
代码片段
删除a
对象是不必要的,并且没有任何影响。 你只能删除实现IDisposable对象,数组没有这样做。 一般规律是,当它管理内存以外资源的.NET类仅实现了IDisposable。 或者,如果它有本身实现IDisposable类类型的字段。
这是另外值得怀疑,你是否应该实施这种情况下,析构函数。 您的示例类是抱着一个相当温和的非托管资源。 通过实施析构函数,你强加给客户端代码的负担使用它。 这在很大程度上取决于使用类是多么容易为客户程序员这样做,这绝不是如果对象预期存活时间长,超出方法体,使using语句无法使用。 你可以让垃圾收集了解内存消耗,它不能跟踪,调用GC :: AddMemoryPressure()。 这也需要的情况下照顾在客户端程序员根本没有使用的Dispose(),因为它实在是太难了。
从标准C ++的准则仍然适用:
调用delete
的自动变量,或者一个一个已经被清理,仍然是一个坏主意。
这是一个跟踪指针释放的对象。 解引用这样是一个坏主意。 随着垃圾回收,内存保持在大约只要任何非弱引用存在,所以你不能意外访问错了对象,但是您仍然无法很可能在任何有用的方式使用该释放的对象,因为它的不变量不再持有。
多重破坏只能在管理对象发生时,你的代码是用非常糟糕的风格,会一直在UB标准C ++(见上文1和4下文)。
显式调用析构函数的自动变量,然后在其位不创建一个新的自动销毁打电话找,仍然是一个坏主意。
一般情况下,你认为对象生存视为从存储器分配(就像标准C ++一样)是分开的。 垃圾回收用于管理释放 - 所以内存是仍然存在 - 但对象已经死了。 不同于标准C ++,你不能去和重用的原始字节的存储内存,因为.NET运行库的部分可以假定元数据仍然有效。
无论是垃圾收集器,也不是“堆语义”(自动可变语法)使用引用计数。
(丑陋的细节:设置一个对象不破.NET运行时自身的关于该对象不变,所以你可能甚至还可以把它当作一个线程监控,但只是让一个丑陋难以了解设计,所以请不要。 “T)。