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.

  • 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
  • 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
  • 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.
  • 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...
  • 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.
  • >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.
  • 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 projects\desttest\desttest\desttest.cpp(18) : warning C4624: 'B' : destructor could not be generated because a base class destructor is inaccessible
    d:\my projects\desttest\desttest\desttest.cpp(18) : error C2248: 'A::~A' : cannot access private member declared in class 'A'
    d:\my projects\desttest\desttest\desttest.cpp(8) : see declaration of 'A::~A'
    d:\my projects\desttest\desttest\desttest.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!
  • 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 Projects\DestTest2003\DestTest2003.cpp(18) : warning C4624: 'B' : destructor could not be generated because a base class destructor is inaccessible

    At a (stack) local variable:

    d:\My Projects\DestTest2003\DestTest2003.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 Projects\DestTest2003\DestTest2003.cpp(18) : error C2248: 'A::~A' : cannot access private member declared in class 'A'
    d:\My Projects\DestTest2003\DestTest2003.cpp(8) : see declaration of 'A::~A'
    d:\My Projects\DestTest2003\DestTest2003.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.)

  • 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).
  • 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.
  • I’ll add a bit to Michael Ruck’s research.

    In Visual C++ 13.10.3077 for x86 (Visual Studio 7.1), the following is possible:

    #include <iostream>

    class Interface
    {
    public:
    virtual void Release() = 0;
    };

    class Base : public Interface
    {
    private:
    ~Base()
    {
    std::cout << "destroying Base" << std::endl;
    }
    };

    class Derived: public Base
    {
    public:
    void Release()
    {
    delete this;
    }
    };

    int main(int, char**)
    {
    Interfacea* o = new Derived;
    delete o;
    }

    It reproduces what ATL is doing behind the scenes (derive a class with an implicit public destructor from a user-defined class, and have a public function that will "delete this"). It compiles with a warning (warning C4624: 'Derived' : destructor could not be generated because a base class destructor is inaccessible) and links. When run, the program fails to call the private destructor (the "destroying Base" text is not written to std::cout).

    Making the Derived destructor explicit causes a compile error (error C2248: 'Base::~Base' : cannot access private member declared in class 'Base').

    And the Holy Standard clearly states in Chapter 12.4 Destructors, Verse 5:

    5 An implicitly-declared destructor is implicitly defined when it is used to destroy an object of its class type (3.7). A program is ill-formed if the class for which a destructor is implicitly defined has:
    — a nonstatic data member of class type (or array thereof) with an inaccessible destructor, or
    — a base class with an inaccessible destructor.

    Conclusion: On some compilers, a private destructor is not sufficient to prevent inheritance. Moreover, such a destructor might not be called during derived object destruction, leading to resource leaks. And to prevent client code from allocating objects on stack or explicitly deleting them, make the destructor protected.
  • 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...
  • Oooh ... how evil would it be to have the delete operator just do a "release" ...
  • Centaur:

    Destructor of Base shouldn't be called in your example because destructor of Interface is not virtual.
  • Your blog is realy very interesting. http://www.g888.com
Page 1 of 2 (17 items) 12