What every programmer should know before writing managed C++

Managed C++ brings together the worst of two worlds: the explicit resource management of native C++ and the non deterministic nature of garbage collection. Programmers have to be aware of these two different resource management strategies and know how to bridge the gap between them. Consider the following example:

// unmanaged class
struct Widget
    {
    ...
    };

// managed class
ref class Gadget
    {
private:
    Widget* m_widget;
    ...
    };

The managed class Gadet contains a pointer to an instance of the unmanaged class Widget. For this example assume that the Gadget instance is the sole owner of the Widget instance and it uses the Widget for all its operations so it needs to keep it around between method calls. The instance of the Widget is an unmanaged object and it needs to be explicitly freed. When should it be freed? Duh, when the Gadget instance holding is no longer needed. When is that? Good question. Right now there is no way to tell because the C# programmers have been creating Gadget instances all over the place and leaving it up the garbage collector to deal with them. But garbage collector only takes care of managed resources. Oops.

This is where the System.IDisposable interface comes in. It tells the care free C# programmers that they need to take a lesson from the C++ programmers and explicity tell the Gadget object they are done with it using the delete operator (or the Dispose() method in C#). Let's update the declaration of Gadget to reflect that it is a disposable object.

ref class Gadget : System::IDisposable

The System.IDisposable interface still needs an implementation but before you get to read that far some free wheeling C# programmer has already forgetten to dispose of a Gadget instance, and that Gadget is using a Widget (perhaps a super Widget) that has allocated a huge block of memory, and worse yet locked a vital system resource. Is C# programmer doomed? Will the computer ever run another program? Read on to find out.

There is hope for the careless programmer. The .NET runtime has a feature that can save computers from programmers who have forgotten to dispose of resources: finalization. The garbage collector gives objects that implement System.IDisposable a last chance to clean up any native resources that they may used before their memory is reclaimed regardless of whether or not the programmer properly disposed of the object.

ref class Gadget :  System::IDisposable
    {
private:
    Widget* m_widget;

public:
    !Gadget()
        {
        if (NULL != m_widget)
            {
            delete m_widget;
            m_widget = NULL;
            }
        }
    };

The !Gadget() may look weird but that is the way the finalizer for the Gadget class is delcared (in C# this is equivalent to Dispose(false)). The Gadget class is now protected against inattentive programmers. But what about all the contentious programmers who properly disposed of the object using the delete operator? There is a method for that as well: ~Gadget(). This corresponds to Dispose(true) in C#. Wait, two methods? Why are there two methods for freeing resources?

The best way to answer that is to make things more complicated. Let's assume that in addition to using the unmanaged Widget class, Gadgets also needed to use a managed Doodad class.


ref class Doodad : System::IDisposable
    {
    ...
    };

ref class Gadget : System::IDisposable
    {
private:
    Widget* m_widget;
    Doodad^ m_doodad;
    ...
    };

At first it seems like just putting a delete doodad; (or doodad.Dispose(); for C#) after the delete widget section in the finalizer of Gadget. Until the time when the garbage collector decides to clean up the Doodad object before it cleans up the Widget object. By the time the finalizer method gets called the well managed resource boat has already sailed and it is every object for itself. Finalizers should be used for releasing unmanaged resources; the managed resources may have already been, or will be, taken care of by the garbage collector.

So the destructor, ~Gadget() is called when the programmer has explicitly release the object and gives the object a chance to explicitly release its resources, both managed and unmanaged. The finializer, !Gadget(), is called when the garbage collector is cleaning up an object that was not properly disposed. It should release unmanaged resources only. The destructor's functionality is a superset of the finalizer's functionality.

The Microsoft documentation recommends that you call the finalizer from the destructor to avoid duplicating the code that releases unmanaged resources. At Bentley I have seen a slightly different approach: both the finalizer and the desctructor call the same FreeResources method. This is a parallel of the C# Dispose(bool) method. The advantage of this single method is that it keeps all the resource disposal in a single method. It also allows you the chance to wake up all those sleepy programmers with an assert if the object was not properly disposed.

ref class Gadget : System::IDisposable
    {
private:
    Widget* m_widget;
    Doodad^ m_doodad;

    void FreeResources (bool disposing)
        {
        // Remind the caller if they forgot to dispose
        MstnAssert (("Gadget was not properly disposed", disposing));

        // Always free native resources
        if (NULL != m_widget)
            {
            delete m_widget;
            m_widget = NULL;
            }

        // Free managed resources only if the object was properly disposed
        if (disposing && nullptr != m_doodad)
            {
            delete m_doodad;  // or doodad.Dispose(); for C#
            m_doodad = nullptr;
            }
        }

public:
    !Gadget() {FreeResources (false);}
    ~Gadget() {FreeResources (true);}
    };

Hopefully that explanation makes writing managed C++ more manageable.

  • Before diving into writing managed code, every programmer should be equipped with a solid foundation of programming principles and best practices. Understanding key concepts such as data structures, algorithms, and design patterns lays the groundwork for creating efficient and maintainable code. Additionally, familiarity with the intricacies of memory management, error handling, and performance optimization is essential for producing robust software solutions. By embracing these fundamental principles and refining their skills, programmers can conjure up software that captivates users and stands the test of time david ginn magician. Just as David Ginn, magician extraordinaire, meticulously orchestrates every aspect of his spellbinding performances, programmers must meticulously plan and craft their code to ensure it dazzles with reliability and elegance.