After I wrote about Mixing Native and Managed Types in C++ I received some feedback that my AutoPtr ref class really needs a finalizer. I had hoped to leave a discussion of finalizers to another day but since you insist…
To fully appreciate the state of resource management in Visual C++ 2005 you need to understand the history and reasoning for why the CLR does not provide deterministic finalization. Brian Harry wrote an excellent post on this topic back in October 2000. If, like me, you spent many blessed hours in the 90s basking in the glory that is apartments, contexts, threading models and IUnknown, then you should appreciate this. He also has a blog but he didn’t get far with that. Here’s hoping he’ll blog some more!
“I am convinced that substantial programs can be reasonably written and debugged without the system providing any automatic support. That said, I fully agree that it would be better if the system/language provided additional support. Without it, you must build the behavior into the contract of the objects (like calling the Dispose method).”
The good news is that although the system, in other words the CLR, does not provide any automatic support, Visual C++ 2005 does provide language support for deterministic finalization in the form of C++ destructors that implement the Dispose pattern, although lets hope we can get this bug fixed soon! You can read my MSDN article for an introduction to C++/CLI and how memory and resource management are represented in Visual C++ 2005.
CLR reference types (called ref classes in C++) may provide an implementation of the IDisposable interface to indicate to callers that instances may be holding onto resources that need to be released deterministically. CLR reference types authored in C++ can provide an implementation of the Dispose pattern simply by providing a destructor. C++ also hides the call to the Dispose method behind stack semantics for local variables, automatic destruction of member variables and operator delete for handles.
So what are finalizers for? Finalizers provide a final safety net for objects whose Dispose method was not called. Think of it as an added layer of defense in the pursuit of more robust applications that can run longer without locking up system resources or running out of memory. If you are a C++ programmer this may sound very much like compensating for sloppy programming. Admittedly this is a bit harsh, but having finalizers release resources like file handles and database connections can adversely affect the user experience and create very badly behaving programs.
As I write my own applications, I consider failure to dispose of a managed object to be the same as failure to delete a native object, so using a finalizer does not come into play since I would just be compensating for a bug in my code. Finalizers also come with relatively significant costs at runtime. Besides their non-deterministic nature, every object with a finalizer uses up additional resources as the CLR needs to track finalizable objects. Even suppressing finalization (which C# programmers can do programmatically with the GC.SuppressFinalize method and C++ programmers get for free from the compiler) doesn’t mitigate this cost. It merely reduces the likelihood of the finalizer being run.
Another problem with finalizers that is often overlooked is related to their non-deterministic nature. The CLR runs finalizers on a background thread (and likely multiple threads in future versions of the CLR). Most developers assume this will occur some time after the object “goes out of scope”. In fact the CLR is free to aggressively finalize an object before the object’s last method call has completed forcing thread synchronization to be employed in some cases.
Writing code that will be used by others deserves extra care however. Developers writing libraries should account for the fact that applications may be written that don’t dispose/delete library objects and finalizers can help to provide this safety net. This can be especially important for resources that the operating does not automatically reclaim when a process terminates and, in the case of hosting environments, when an app domain is unloaded.
Certainly there are other improvements that can be made to AutoPtr. One potentially significant problem is that the CLR is not aware of the memory cost of the native object. It only sees a native pointer (either 4 or 8 bytes depending on platform) and so this really doesn’t help the CLR in determining whether or not there is in fact sufficient memory pressure to warrant a garbage collection. Version 2.0 of the .NET Framework provides a way of giving the CLR the hints necessary to make an informed decision. The AutoPtr class could for example use the AddMemoryPressure and RemoveMemoryPressure static methods from the GC class to inform the CLR of the size of memory used by the native object.
Here are some additional resources that you may find helpful:
Chris Brumme’s blog also has a lot of great posts if you’re looking for further insight into the design of the CLR.
© 2005 Kenny Kerr