Tuesday, July 31, 2012

A Blind Spot in C++ Destructor

I have known for long that in the constructors of a class in C++ and literally any major modern Object Oriented programming languages, the virtual methods of the class are not to work as one might expect - in fact only the method defined in the class that contains the constructor that is being executed is to be executed from within the constructor rather than the overriding one located in the class of which the object to be created is an instance, the reason simply being that the run-time information of the derived class on which the invocation of the overriding method(s) depends at this stage hasn't been created as the order of constructing is always from the base class to the derived. Note this is one of the questions I was given in a interview with google at Shanghai and I didn't get quite right. I have fully understood the significance of it since then as it's so fundamental and it will both enhance your code at a potentially architectural level and save you time debugging.

However, recently I found it was not the end. Despite the simplicity of that, it seems I didn't learn a proper lesson from it. This morning I encountered a similar issue, not with constructor but with destructor (which in C# dialect may also be known as 'finaliser'). I finally decided to extend a dialog class to deal with some importing and exporting stuff so that  objects of a layer and of particular types recently implemented could be exchanged with files of GIS format as those of all the other layers were, and in the extended class I defined a exporter instance for polylines in addition to the existing one in the base class for polygons. I didn't forget to write the overriding methods for closing the files and doing other cleanup, nor did I forget to call the corresponding base methods from the overriding methods as appropriate or mark all the relevant methods 'virtual'. However the separate file for polylines never got exported correctly. As it had worked properly when done by simply adding logic to the base, so I was pretty sure the methods are logically correct. After quite a bit of debugging, comparisons and analysis, I found the problem was indeed to do with finalising which I had been suspecting most. Since the calling of finalising method was only made in the destructor of the base class, the overriding finalising method in the derived class never got called. The principle behind is the almost same as that behind the constructor, the only difference is the order is reversed - the destructor in the base class as is called last never has any knowledge of the derived class and is unable to call the overriding method.
The conclusion, to be simple, is that as you should avoid whatever attention you should pay with regards to virtual method calls from a constructor should equally be paid to those from a destructor.

An example of how this plays a part in a real programming paradigm,
The following is a typical and standard way of defining a class that implements IDisposable interface

class DisposableObject : IDisposable
{
    protected bool disposed;  // or any member that can indicate if the 
                              // object has been finalised in any way
                              // however unlike what's suggested by most 
                              // of the resources it should be made accessible 
                              // from a derived class to make this inherited 
                              // disposal implementation to work (unless a 
                              // separate one is set up in the derived class)


    ~DisposableObject()
    {
        Dispose(false);  // called from destructor
    }


    // As it's an implementation of IDisposable method, it has to be public
    // And as it's normally called on the interface instance, it doesn't have
    // to be declared virtual
    public void Dispose()
    {
        Dispose(true);  // called from diposing method
        GC.SuppressFinalize(this);
    }


    // Dispose of managed resources, this is a safe and logic method
    // that has already been widely practised, 
    //   http://msdn.microsoft.com/en-us/library/system.idisposable.dispose.aspx
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose of managed resources
                // ...
            }
            // cleanup unmanaged resources
            // ...
            disposed = true;
        }
    }
}


However the derived disposable class is rarely looked at. According to the above analysis of how destructors work, it should be like,

class DerivedDisposableObject : DisposableObject
{
    ~DisposableObject()
    {
        Dispose(false);  // must not rely on the base class to dispose if an
                         // overriding method is defined; the base Dispose method
                         // will be called from the base destructor but its 
                         // internal will not be unnecessarily executed as the 
                         // flag of 'disposed' has already been flipped here
    }


    // void (IDispose.)Dispose() is not necessary (unless it's different to 
    // the base) since it's not at the finalising stage and the overriding method     
    // would be properly invoked from base class


    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose of managed resources
                // ...
            }
            // cleanup unmanaged resources
            // ...
            // DO NOT set 'disposed' to true as the base method needs it
            base.Dispose(disposing);
        }
    }
}

No comments:

Post a Comment