Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

Private Destructors

Private Destructors

  • Comments 17
Yesterday, I mentioned that Michael Ruck had complained that I'd made the destructor on my CFooBase class private, and he wondered why on earth I had done it.

Skywing answered in the comments but it bears a bit more discussion.

The simple answer to why I made the destructor private was that I didn't want anyone to be able to destroy the object.

????

That's right.  You see, CFooBase is a reference counted objects.  And you NEVER, EVER want to allow someone to delete a reference counted object.

This is because you don't own the lifetime of the reference counted object.  Really, you don't.  You own the lifetime of your reference to the object, but you have no way of controlling who else is taking a reference to the object.  So the object may live well after you're done with it.

For example, consider the following case:

void MyFunction(void)
{
    CFooBase *myFoo;

    myFoo = new CFooBase();
    <Do Some Stuff>
    delete myFoo;
}

Seems pretty straightforward, right?

Well, no.  The reason is that you have no idea what happened in the <Do Some Stuff> section.  For example, consider:

void MyFunction(void)
{
    CFooBase *myFoo;

    myFoo = new CFooBase();
    hr = RegistrationFunction->RegisterForNotifications(myFoo);  // RegistrationFunction takes a reference.
    <Do Some Stuff>
    hr = RegistrationFunction->UnregisterForNotifications(myFoo); // Releases the reference taken earlier
    delete myFoo;
}

What's wrong here?  Well, what happens if a notification was being processed during the call to UnregisterForNotifications?  In that case, the notification logic would take ANOTHER reference to the myFoo object (to ensure that the object remains alive during the duration of the callback).  But by deleting the myFoo directly, you're deleting the object out from under the registration function.

If, on the other hand, you make the destructor for myFoo private, then the call to delete myFoo returns an error, which forces you to rewite the code to look like:

void MyFunction(void)
{
    CFooBase *myFoo;

    myFoo = new CFooBase();
    hr = RegistrationFunction->RegisterForNotifications(myFoo);  // RegistrationFunction takes a reference.
    <Do Some Stuff>
    hr = RegistrationFunction->UnregisterForNotifications(myFoo); // Releases the reference taken earlier
    myFoo->Release();    // Remove my reference to the myFoo
}

In other words, making the destructor private forces you to use the correct release pattern for refcounted object.

Of course, the next problem that comes up is the question of deterministic finalism - if the object in question is holding some external resource open and you need to ensure that it's closed its resources.

Well, the CLR IDisposable pattern comes in quite handy here.  That allows the caller to notify the object that it's done with the object.  Of course, it's also responsible for dealing with the consequences...

The bottom line is that once you decide to use reference counted objects, you don't control the lifetime of the object, all you do is control the lifetime of a reference to the object.  And declaring the destructor private forces you to recognise this.