В C++/CLI, A ref
деструктор класса является абстракцией над Dispose pattern.
Следующий C++/класс CLI, при компиляции:
public ref class Test
{
public:
Test() { System::Console::WriteLine("ctor"); }
~Test() { System::Console::WriteLine("dtor"); }
static void Foo()
{
auto foo = gcnew Test();
foo->~Test();
}
};
декомпилирует к следующему C# код (C# семантика гораздо ближе к нижележащему кода IL, так что это хороший способ визуализировать то, что происходит):
public class Test : IDisposable
{
public Test()
{
Console.WriteLine("ctor");
}
private void ~Test()
{
Console.WriteLine("dtor");
}
public static void Foo()
{
new Test().Dispose();
}
protected virtual void Dispose([MarshalAs(UnmanagedType.U1)] bool A_0)
{
if (A_0)
{
this.~Test();
}
else
{
this.Finalize();
}
}
public virtual void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize((object)this);
}
}
Вы можете увидеть, что шаблон размещения реализован автоматически компилятором.
~Test
«destructor» скомпилирован для частного метода, и для вас создана реализация IDisposable::Dispose
. По какой-то причине компилятор также вызывает (пустой) финализатор.
Также, как вы можете видеть в статическом методе Foo
, foo->~Test();
просто переведен на звонок Dispose
. Компилятор не позволит вам напрямую позвонить foo->Dispose();
.
Но стандарт подход называют «деструктор» (и, следовательно, метод Dispose
) заключается в использовании delete
ключевого слова: delete foo;
такого же, как foo->~Test();
в C++/CLI, когда foo
является управляемой ручкой.
Обратите внимание, что в этом примере, вместо написания:
auto foo = gcnew CppCli::Test();
foo->Whatever();
delete foo;
Вы можете использовать стек семантику и написать:
Test foo;
foo.Whatever();
foo.~Test();
будет вызываться, когда foo
выходит из области видимости, как в регулярной C++.
Для полноты, вот как все это взаимодействует с финализаторами. Давайте добавим один:
public ref class Test
{
public:
Test() { System::Console::WriteLine("ctor"); }
~Test() { System::Console::WriteLine("dtor"); }
!Test() { System::Console::WriteLine("finalizer"); }
};
Это декомпилирует к следующему C# -like код:
public class Test : IDisposable
{
public Test()
{
Console.WriteLine("ctor");
}
// This is the real finalizer
~Test()
{
this.Dispose(false);
}
// This is what C++/CLI compiles ~Test to
// Let's call this MethodA
private void ~Test()
{
Console.WriteLine("dtor");
}
// This is what C++/CLI compiles !Test to
// Let's call this MethodB
private void !Test()
{
Console.WriteLine("finalizer");
}
[HandleProcessCorruptedStateExceptions]
protected virtual void Dispose([MarshalAs(UnmanagedType.U1)] bool A_0)
{
if (A_0)
{
this.~Test(); // MethodA, NOT the finalizer
}
else
{
try
{
this.!Test(); // MethodB
}
finally
{
base.Finalize();
}
}
}
public virtual void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize((object)this);
}
}
Обратите внимание, что для дополнительной путаницы, в C# финализации является ~Test()
, что другое от private void ~Test()
функция, которую компилятор C++/CLI генерирует для вашего деструктора.
Большое спасибо за подробное объяснение, действительно полезно. – Geek
Вы также должны заметить, что синтаксис '~ Test()' не является стандартным. Канонический способ распоряжаться управляемым объектом - использовать 'delete', так же как и объект C++. –
@DavidYaw, вы правы, это важный момент, спасибо! –