Private Destructors

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.

Comments

  • Anonymous
    July 01, 2005
    Hi, Larry,

    I've enjoyed reading your posts over time, and I wanted to comment on this one.

    I've had really good luck using the boost shared_ptr<T> class to let me basically forget about resource management.

    Since the shared_ptr itself is a stack object, I get the reference incrementing for free, and I can never forget to release the reference, since the compiler does it for me.

    RAII is the biggest thing I miss in C#/Java land :)


    BTW, another reason for private dtors is to prevent your object from being created on the stack, as I'm certain you know.

    -- bab

  • Anonymous
    July 01, 2005
    Still don’t see how you can derive CComObject<T> from a class T whose destructor is private. A standard-conforming compiler ought to give out an error message along the lines of:
    error: "Base::~Base()" is inaccessible

  • Anonymous
    July 01, 2005
    I also make copy constructors and assignment operators private to make sure derived classes don't try to do anything clever...

    I have a standard macro that defines the prototypes and makes them private. I use this for every class I write. It forces you to take a step back and think before exposing them.

  • Anonymous
    July 01, 2005
    Centaur, I don't know how it works, I do know that it does work.

    And I trust the Microsoft C compiler on this one...

  • Anonymous
    July 01, 2005
    Steve:

    There's a base class called noncopyable in the Boost project that I use for that. The benefit to the Boost way is that the name "noncopyable" ends up in the innaccessable member compiler error.

  • Anonymous
    July 01, 2005
    >you need to ensure that it's closed its resources.

    This correct use of apostrophes is shocking in someone who calls himself a programmer. Beware: repeat offenders may come home to find that the real programme'rs have taken their revenge: the children encrypted and the dog translated into APL.

  • Anonymous
    July 01, 2005
    Well it seems the private destructor behavior depends upon the compiler in use. The GNU compiler collection does not allow inheritance from a class, whose destructor is private.

    It stops compilation with an error that the base class destructor is private and inaccessible in the destructor of the inheriting class. (This even in the case, when the deriving class doesn't have a destructor specified.)

    After testing this with Visual Studio 2005, the C++ compiler (Version 14.00.50215.44) also fails with the following errors:

    d:my projectsdesttestdesttestdesttest.cpp(18) : warning C4624: 'B' : destructor could not be generated because a base class destructor is inaccessible
    d:my projectsdesttestdesttestdesttest.cpp(18) : error C2248: 'A::~A' : cannot access private member declared in class 'A'
    d:my projectsdesttestdesttestdesttest.cpp(8) : see declaration of 'A::~A'
    d:my projectsdesttestdesttestdesttest.cpp(7) : see declaration of 'A'
    This diagnostic occurred in the compiler generated function 'B::~B(void)'

    So it seems this is one of the areas, where previous MSVC compiler versions were lacking...

    The source for both tests:

    class A
    {
    ~A() {};

    public:
    A() {};
    };

    // Fails in GCC and MSVC 2005, B can't derive from A, as the destructor is inaccessible!
    class B : public A
    {
    public:
    B() {};
    };

    int main(int argc, char* argv[])
    {
    // A a; // Fails - ok
    B b; // Fails to on both GCC and MSVC 2005

    return 0;
    }

    Anyways - nice topic Larry!

  • Anonymous
    July 01, 2005
    Whats more interesting, I just tested this with VS2003 (compiler version 13.10.3077 for x86) and it fails too - but differently:

    At the class:

    d:My ProjectsDestTest2003DestTest2003.cpp(18) : warning C4624: 'B' : destructor could not be generated because a base class destructor is inaccessible

    At a (stack) local variable:

    d:My ProjectsDestTest2003DestTest2003.cpp(23) : error C2262: 'b' : cannot be destroyed

    So if no destructor is written for B, the compiler doesn't fail for the class itself but for each stack usage.

    If one adds a destructor for B, the compiler fails with the following error:

    d:My ProjectsDestTest2003DestTest2003.cpp(18) : error C2248: 'A::~A' : cannot access private member declared in class 'A'
    d:My ProjectsDestTest2003DestTest2003.cpp(8) : see declaration of 'A::~A'
    d:My ProjectsDestTest2003DestTest2003.cpp(7) : see declaration of 'A'

    And if one removes the destructor and allocates an instance of B on the heap, the compiler does all well and even links the code - of course this would leak memory as you can never invoke operator delete on the object (at least not outside of A, which has the private destructor.)

  • Anonymous
    July 03, 2005
    After doing some research, I believe the destructors should be marked as protected if you intend to subclass them. If you don't intend to sublcass them, marking the destructor as private is ok. Apparently there was a compiler bug in VC6 where the "privateness" of destructors was ignored, which is why the above code probably worked for someone at some point in time.

    It is possible that the ATL_NO_VTABLE syntax performs some magic that allows subclassing to work in an expected manner with a private destructor, but pure C++ doesn't allow it.

    That being said, the "test" program that Michael came up with should never work. The whole point of making the destructor unavailable is to prevent people from creating the object on the stack (these are reference counted objects; they get deleted when nobody has a reference to the object, not when they leave scope).

  • Anonymous
    July 03, 2005
    Mirobin,

    the test was just to validate the visibility rules of C++ for myself. It had nothing to do with reference counting - even if the test classes were reference counted they still must obey the C++ visibility rules...

    I fully understand the requirements to prevent others from invoking delete or creating instances on the stack. The private destructor in a base class just got my attention, as it didn't match my knowledge. I made the tests to see if I remembered the rules properly.

  • Anonymous
    July 03, 2005
    The comment has been removed

  • Anonymous
    July 03, 2005
    if you want to keep people from deleting the object yet allow construction of an instance on the stack, then you may want to make operator delete private, rather than the destructor...

  • Anonymous
    July 04, 2005
    Oooh ... how evil would it be to have the delete operator just do a "release" ...

  • Anonymous
    July 05, 2005
    Centaur:

    Destructor of Base shouldn't be called in your example because destructor of Interface is not virtual.

  • Anonymous
    August 25, 2005
    Your blog is realy very interesting. http://www.g888.com

  • Anonymous
    January 18, 2009
    PingBack from http://www.keyongtech.com/4701593-why-does-delete-not-delete

  • Anonymous
    June 12, 2009
    PingBack from http://cellulitecreamsite.info/story.php?id=9082