共用方式為


Desctructor, finalizer, and Dispose - Part2.C++/CLI in Whidbey

I changed the program in previous post to use new Whidbey syntax.

using namespace System;

ref class RefT
{
public:
RefT () {Console::WriteLine ("RefT::RefT");}
~RefT () {Console::WriteLine ("RefT::~RefT");}
!RefT () {Console::WriteLine ("RefT::!RefT");}
};

value class ValueT
{
//constructor is not allowed for value type
//destructor is not allowed for value type
};

int main()
{
{ //1. finalizer will be called in asynchronizied fashion
RefT ^ rrt = gcnew RefT;
}

     { //2. Dispose is called at “delete” and finalizer is suppressed
RefT ^ rrt = gcnew RefT;
delete rrt;
}

     { //3. Dispose is called at end of the block and finalizer is suppressed
RefT rt;
}

{
ValueT vt;
}

{
ValueT ^ pvt = gcnew ValueT;
}

     {
ValueT * pvt = new ValueT;
}
return 0;
}

First thing to notice is __gc and __value are replaced by ref and value, this is definitely clearer; then RefT now could have another method !RefT. What does this new methods do and how is related to the other ones? Having checked IL generated by Whidbey cl, I found RefT is translated into something like:

class RefT : IDisposable
{
//constructor
RefT ()
{
Console.WriteLine (“RefT::RefT”);
}

    //Dispose methods
Dispose (bool disposing)
{
if (disposing)
{
~Ref();
}
else
{
try
{
!Ref();
}
finally
{
Object.Finalize();
}
}
}

 Dispose ()
{
Dispose (true);
SuppressFinalize (this);
}

  //finalizer
Finalize ()
{
Dispose (false);
}

  //body of Dispose and Finalize
~RefT ()
{
Console.WriteLine (“RefT::~RefT”);
}

  !RefT ()
{
Console.WriteLine (“RefT::!RefT”);
}

}

Basically a reference type with destructor (method starts with ~) implements IDisposable interface. Its destructor becomes Dispose method; we could also define finalizer for a reference type using the “!” syntax. According to my test, those two are independent to each other. If I only define “~” function, not the “!” one, the class will only have Dispose methods, no Finalize will be generated although Dispose still call SuppressFinalize; if I only define “!” method without the “~” one, I got a compiler warning and a class which has a finalizer but doesn't implement IDisposable.

Other new things include using tracking handle (“^”) instead of pointer for reference to GC type and gcnew to indicate the memory is allocated in managed heap.

All the new designs make CLR concepts (reference/value type, Finalize, Dispose, managed heap) first class citizen in C++. As a CLR team member, I think this is much clearer than special annotation or mapping new concepts to existing features which have different semantics. However, from a C++ user's point of view, the changes seem to draw C++ closer to C#. I'm not sure if everyone would love them.

Back to finalization: for tracking handles, things are still similar to pointers in V1.X. If delete is not called on a tracking handle (like the first block in main), some time in the future finalizer will run and GC will collect the object; if "delete" is called (like the 2nd block in main), Dispose (~ function) is called and finalizer is suppressed, the object will still be GCed later.

The 3rd block in main is the most interesting one: we could create reference type object “on stack” (of course, the object is still in heap, we just save a reference in stack) and this “stack object” has the traditional C++ finalization semantics: when the variable goes out of scope, its destructor (Dispose method) will be called automatically if it has one. The generated IL for block 3 looks to be something like this:

RefT r = new RefT;
try
{
}
finally
{
((IDisposable)r).Dispose ();
}

This looks almost same as C#'s using statement. Implementing C++'s automatic destruction by Disposable pattern is a brilliant idea. I just worry the new “stack object” syntax will create new confusion about where the object really lives. I tend to think the stack object is a holder, similar to auto_ptr, which holds reference to an object in heap and will delete the object when it goes out of scope. I did have question at the beginning about whether this holder tracks ownership of the object (like auto_ptr) or does ref counting (like traditional smart pointer) to make sure if multiple holders reference the same object, only the last one dispose the object. But there seems to be no way to make two holders to point to the same object. E.g: this code doesn't compile with compliant “operator=” isn't available for RefT, even if you define operator= for RefT, it only applies to the object in heap, not the holder on stack:

RefT rt1;
{
RefT rt2;
rt1 = rt2;
}
//I assumed rt2 would reference to a disposed object here

After all this “holder” is more of syntax sugar other than a real smart pointer class, it's easy to guarantee they don't represent the same object in heap. My worry might be unnecessary, but it's just an example how it could be confused.

With so many changes, it's inappropriate to call it managed extension to C++ anymore. Now people refer the new language mostly as C++/CLI. I really feel sorry for those who have to rewrite their C++ code for .NET platform again and again. But I think Whidbey lays down a foundation which could last for generations.

Comments