Meet C++/WinRT 2.0: Deferred Destruction

In the previous installment I described how (dangerous) direct allocations are now reliably diagnosed and how this required a public destructor. I also hinted at how having a public destructor enables another feature. That feature is deferred destruction. This is the ability to detect the final Release call on an object and then take ownership of that object to defer its destruction indefinitely. Recall that COM objects are intrinsically reference counted and that reference count is managed via the AddRef and Release functions on IUnknown. Traditionally, a COM object’s C++ destructor is invoked once the reference count reaches zero:

uint32_t WINRT_CALL Release() noexcept
{
    uint32_t const remaining = subtract_reference();

    if (remaining == 0)
    {
       delete this;
    }

    return remaining;
}

The “delete this;” statement will call the object’s destructor before freeing the memory occupied by the object. This works well enough, provided you don’t have to do anything interesting in your destructor:

using namespace Windows::Foundation;

struct Sample : implements<Sample, IStringable>
{
    hstring ToString() const;

    ~Sample() noexcept
    {
        // Too late to do anything interesting
    }
};

For one thing, a destructor is inherently synchronous. You cannot switch threads, perhaps to destroy some thread-specific resources in a different context. You cannot reliably query the object for some other interface that may be needed to free certain resources. The list goes on. A more flexible solution is needed for those cases where your destruction may be non-trivial. Enter the final_release function.

struct Sample : implements<Sample, IStringable>
{
    hstring ToString() const;

    static void final_release(std::unique_ptr<Sample> ptr) noexcept
    {
        // First stop...
    }

    ~Sample() noexcept
    {
        // Called when unique_ptr deletes the object
    }
};

The C++/WinRT implementation of the Release function has been updated to call final_release when the object’s reference count reaches zero. In this state, the object knows that there are no further outstanding references and it now has exclusive ownership of itself. It can thus transfer ownership of itself to the static final_release function. In other words, the object has transformed itself from one that supports shared ownership into one that is exclusively owned. The unique_ptr has exclusive ownership of the object and so it will naturally destroy the object – hence the need for a public destructor – when the unique_ptr goes out of scope, provided it is not moved elsewhere first. And that’s the key. The object may be used indefinitely, provided the unique_ptr keeps the object alive. You might move the object elsewhere as follows:

struct Sample : implements<Sample, IStringable>
{
    hstring ToString() const;

    static void final_release(std::unique_ptr<Sample> ptr) noexcept
    {
        gc.push_back(std::move(ptr));
    }
};

Think of this as a more deterministic garbage collector. Perhaps more practically and more powerfully, you can turn the final_release function into a coroutine and handle its eventual destruction in one place while being able to suspend and switch threads as needed:

struct Sample : implements<Sample, IStringable>
{
    hstring ToString() const;

    static fire_and_forget final_release(std::unique_ptr<Sample> ptr) noexcept
    {
        co_await resume_background(); // Unwind the calling thread

        // Safely perform complex teardown...
    }
};

A suspension will point will cause the calling thread, that originally initiated the call to the Release function, to return and thus signal to the caller that that the object it once held is no longer available through that interface pointer. UI frameworks often need to ensure that objects are destroyed on the specific UI thread that originally created the object. This feature makes fulfilling such a requirement trivial because destruction is separated from releasing the object.

Stay tuned for more. And be sure to watch our Build talk for more about C++/WinRT 2.0!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s