Deterministic Finalization III – Benefits, part 1

I'm pretty angry at right now (or maybe I'm just angry at myself), as it completely nuked a post I had composed, because my session had timed out on it.  I went to post, and it asked me to log in, and in the process destroyed a lot of work.

I'll try to put my frustrations out of mind, and continue with my increasingly tardy discussion of C++ DF.  I've already discussed the CLR Dispose pattern, and the C++ destructor syntax.  Originally, I'd planned for this third part to be about interaction between those two models, but I think I'll save that for later, and instead start talking about the benefits of C++ DF, by way of discussing a comment on my previous blog post.

A bit of discussion about Dispose brought up an interesting point - in C++, now, we've disabled the ability to call the Dispose function directly.  Instead, you call it thusly:

ref class MyClass{
  ~MyClass(){} //overrides IDisposable::Dispose()

int main(){
  MyClass^ mc = gcnew MyClass();
  //do stuff

  mc->Dispose(); //error!
  mc->~MyClass(); //legal, calls Dispose()
  delete mc; //also legal, also calls Dispose()

The options left open to the user are, I think, a little more familiar.  However, there's a large caveat included in this discussion.  Imagine, in that innocuous comment titled "do stuff," that I throw an exception.  I haven't handled it in my code snippet, so I'm likely to run into issues.  The most obvious way to handle this would be to wrap our instantiation in a try/finally:

int main(){
  MyClass^ mc = gcnew MyClass();
  try {
    //do stuff, maybe even throw an exception
  } finally {
    delete mc; //dispose, regardless of execution path

That works, but it's likely to get nasty very quickly when you have multiple disposable objects, as you should be nesting try/finallys (to guard against exceptions in an object Disposer, for example).  C# has a nifty piece of syntax set up to handle this, the using statement (not to be confused with the using directive).  You can scope out the code example in that article for more information on it.  In C++, however, we've developed our own bit of syntactic sugar to handle disposable objects cleanly: ref types on the stack.  With a nod to the example above, here's a quick look at ref types on the stack:

#using <System.Drawing.dll>
using namespace System::Drawing;

int main(){
  Font f1("Arial", 10.0f);
  Font f2("Courier New", 8.0f);
  //do stuff with these fonts
} //Dispose called automatically, even with exceptions

This is a syntax that should be intimately familiar to C++ users - what's more, if you look at the IL, it actually behaves the way they will expect.  That is, your disposer gets called even if an exception is thrown during the "do stuff" phase.  Ref types on the stack aren't really anything special, they're still a ^ underneath, it's just a lightweight "shim," with a few added bonuses.  However, this syntax does represent a slight problem - there are no BCL methods that take a font-on-the-stack, because this whole "on the stack" thing is a C++ convention.

To handle this, (pun intended) we introduced the % (Anders-of) operator.  This basically gets you the handle underneath our implementation.  So, you can have a ref type on the stack, and still pass it to functions that expect a handle to that type.  It's used exactly like the & (address-of) operator, which is pleasantly analogous, in light of the & reference type and the % tracking reference type.

#using <System.Drawing.dll>
using namespace System;
using namespace System::Drawing;

void foo(Font^ f);

int main(){
  Font f1("Arial", 10.0f);
  foo(%f1); //foo requires a Font^

Why not use something similar to the boxing conversion?  (Is it some kind of coincidence that my post on boxing was also apparently nuked at some point?)  Anyhow, there's a very good reason for not using a boxing-style conversion, and it's something new we've implemented for Whidbey C++: deep copy semantics.  (That is, copy constructors for ref types.)

We're starting to stray outside the bounds of this conversation, however.  We'll save copy ctors for another day.  Next time, I'll get into more benefits of DF.  Also still to come: how C++ DF interacts with the CLR Dispose pattern.

Comments (6)
  1. Anonymous says:

    Of course the C++ way to handle this is a smart pointer:

    auto_gcptr<Font> font = gcnew Font();

    then when the autoptr goes out of scope it will be destructed.

    The implementation of auto_gcptr should be pretty easy but it would be nice to have this in the microsoft extensions to std.

    Now that is a big improvement over nesting usings to Dispose of a number of elements. and we get deterministic finalization by combining the scoped destructor with the Dispose pattern

  2. Anonymous says:

    Why not allow the compiler to automatically insert the % when calling a function that expects a handle.

    Having to write foo(%f1) complicates the abstraction needlessly. The compiler knows that f1 is a handle in reality, so it could do this without any intervention from the programmer.

  3. Anonymous says:

    I didn’t want to give the impression that I was suggesting the use of nesting "usings" – our solution to handling disposable items is to have ref types on the stack.

    As to not allowing the compiler to insert the %, it comes back to the copy constructor. As we are going to have copy constructors for ref types, it will be possible to have a foo(R r) function signature.

    It would confuse matters to have this sort of implicit boxing conversion for ref types as well.

Comments are closed.

Skip to main content