C++/CLI中的确定性垃圾回收与mdl.net中Element
C++/CLI中的确定性垃圾回收
托管语言中自动垃圾回收导致回收的时机是不确定的,只有托管C++实现了确定性的回收机制,在一本名著中这么写到(我翻译过来的):
析构器和终结器的IL实现
在我们对垃圾收集的介绍中,您看到了GC如何在清理对象之前自动调用终结器。很明显。NET直接支持终结器。但没有提到析构器。这是因为NET 框架的自动垃圾收集内存模型不利于确定性析构器的概念。在这种背景下,C++/CLI使用Dispose模式来实现析构器和终结器语义。
C++/CLI Dispose模式使用以下规则:
■如果一个class有终结器(!typename)或析构器(~typename),编译器为该类生成Dispose(bool)方法。编译器生成的代码检查bool,如果传递了true值,它会调用析构器(~typename),如果传递了false值,它会调用终结器(!typename)。
■对于任何定义析构器(~typename)的对象,编译器自动实现该对象的System::IDisposable接口,并生成满足该接口的Dispose()方法。此编译器生成的Dispose方法调用Dispose(true),以便执行析构器(~typename)。然后,它调用GC::SuppressFinalize(this),它将该对象从Finalization队列中删除,因为该对象现在其析构器已被调用,因此无需再使用终结器。
■对于任何定义终结器的对象(!Typename),编译器自动生成一个Finalize方法,该方法包含了System::Object::Finalize()。此编译器生成的Finalize方法调用Dispose(false),以便终结器(!Typename)被执行。
namespace test
{
public ref class A
{
public:
A() {}
private:
!A() {}
};
public ref class B
{
public:
B() {}
private:
~B() {}
};
public ref class C
{
public:
C() {}
private:
!C() {}
~C() {}
};
}
以上代码编译后的IL反编译如下,可以看到编译器塞入了哪些内容,符合上述的解释:
QQ20220409-142325.png请注意,析构器和终结器都可以放入私有部分,因为delete和GC不是直接访问他们,这和标准C++不同。
另外,如果想要手工执行回收,不需要调用终结器,则GC::SuppressFinalize(Object^)
设置就能删除终结器的自动调用。而System::GC::ReRegisterForFinalize(Object^)
则会将object的句柄重新加入Finalization终结器列表,就又可以自动回收了。
比如,在mdl.net中最常用的Element中AdjustFinalizeRequirement用于调整是否需要调用终结器(反编译代码):
internal unsafe void AdjustFinalizeRequirement()
{
int num;
GC.SuppressFinalize(this);
ref byte modopt(IsExplicitlyDereferenced) pinned numRef = (ref byte modopt(IsExplicitlyDereferenced)) &(this.ElementHandle[0]);
EditElementHandle* handlePtr = numRef;
if ((0L == Bentley.DgnPlatform.ElementHandle.PeekElementDescrCP((ElementHandle modopt(IsConst)* modopt(IsConst) modopt(IsConst)) handlePtr)) &&
(0L == Bentley.DgnPlatform.EditElementHandle.GetIElementState(handlePtr)))
{
num = 0;
}
else
{
num = 1;
}
if (((byte) num) != 0)
{
GC.ReRegisterForFinalize(this);
}
}
如果ElementHandle中没有元素描述符和IElementState也为空的话,就没必要调用终结器。