New in C++/WinRT: Mastering Strong and Weak References

Today I’d like to share another feature of C++/WinRT available in build 17709 of the Windows SDK that really helps to build more complex systems simply and correctly. Distinguishing between strong and weak references is often a necessity in reference-counted systems like WinRT. Knowing how to manage those references correctly can mean the difference between a reliable system that runs smoothly and one that crashes unpredictably. C++/WinRT makes this very simple with some helper functions with deep support in the projection.

While I have already talked about how to work with implementations and understand weak references, reference cycles can wreak havoc on well-intentioned designs and until recently there hasn’t been a simple way to deal with that when it comes to your own implementations. Of course, using neither strong nor weak references is just as problematic. Consider this standalone example:

struct App : implements<App, IInspectable>
{
    hstring m_value{ L"Hello world" };

    IAsyncOperation<hstring> Async()
    {
        co_await 5s;
        co_return m_value;
    }
};

It seems simple enough. The App class has an Async method that eventually returns the value. Now consider this main function:

int main()
{
    init_apartment();
    auto app = make_self<App>();

    auto async = app->Async();

    auto result = async.get();
    printf("%ls\n", result.c_str());
}

Does this seem reasonable? Here’s what happens:

1. The app is created.
2. The async object is created (pointing to the app).
3. The get function blocks for a few seconds and then returns the result.
4. No problems.

But what if the app object is destroyed before the async operation completes? Consider this one-line change:

int main()
{
    init_apartment();
    auto app = make_self<App>();

    auto async = app->Async();
    app = nullptr; // <-- oops

    auto result = async.get(); // <-- boom!
    printf("%ls\n", result.c_str());
}

That should be harmless right? After all, the app object is not referred to after that point. Oh, but it is. The async operation attempts to copy the value stored inside the app (via its implicit this pointer). After all, the coroutine is a member function and considers it the current object that it naturally has access to. Here’s what happens now:

1. The app is created.
2. The async object is created (pointing to the app).
3. The app is destroyed.
4. The get function blocks for a few seconds and then… BOOM!

As soon as it attempts to access the variable inside the app object it will crash or do something entirely undefined. The solution is to give the async operation – the coroutine – its own strong reference to the app object. As it stands, the coroutine effectively holds a raw this pointer to the app object. That is not enough to keep the app object alive. The App class may be updated as follows:

struct App : implements<App, IInspectable>
{
    hstring m_value{ L"Hello world" };

    IAsyncOperation<hstring> Async()
    {
        auto strong = get_strong(); // <-- keep alive

        co_await 5s;
        co_return m_value;
    }
};

Now everything works as expected. All outstanding references to the app may disappear but the coroutine ensures that its dependencies are stable. Of course, a strong reference may not always be desired. A weak reference is also possible as follows:

struct App : implements<App, IInspectable>
{
    hstring m_value{ L"Hello world" };

    IAsyncOperation<hstring> Async()
    {
        auto weak = get_weak(); // <-- maybe keep alive

        co_await 5s;

        if (auto strong = weak.get())
        {
            co_return m_value;
        }
        else
        {
            co_return L"";
        }
    }
};

In this case, the coroutine holds a weak reference that will not keep the app from being destroyed if no other strong references remain. The coroutine must then check whether a strong reference can be acquired before using the member variable.

Of course, lifetime issues are not limited to coroutines or concurrency. You can land up in the same boat using traditional callbacks. Consider another standalone example, this time a hypothetical Window class that sports a single event:

struct Window
{
    event<EventHandler<int>> m_event;

    void PointerPressed(EventHandler<int> const& handler)
    {
        m_event.add(handler);
    }

    void RaisePointerPressed()
    {
        m_event(nullptr, 123);
    }
};

I’m using the Windows::Foundation::EventHandler delegate but the parameters don’t matter. It could just as well be any other delegate. Handlers may be registered, all of which will be called when the event is raised. Now consider a typical app that wants to respond to such events:

struct App : implements<App, IInspectable>
{
    hstring m_value{ L"Hello world" };

    void Register(Window& window)
    {
        window.PointerPressed([&](auto&&...)
        {
            printf("%ls\n", m_value.c_str());
        });
    }
};

In many cases, this is in fact perfectly reasonable. For graphical applications, the App object typically outlives the framework that may raise such events. Still, that’s not necessarily the case and it’s important to know how to deal with that. Consider this main function:

int main()
{
    init_apartment();
    Window window;
    auto app = make_self<App>();

    app->Register(window);

    window.RaisePointerPressed();
}

It seems reasonable enough and you should expect this to work reliably, but consider this change:

int main()
{
    init_apartment();
    Window window;
    auto app = make_self<App>();

    app->Register(window);
    app = nullptr; // <-- oops

    window.RaisePointerPressed(); // <-- boom!
}

Suddenly the event handler explodes. Take a closer look at the handler’s registration:

window.PointerPressed([&](auto&&...)
{
    printf("%ls\n", m_value.c_str());
});

The lambda automatically captures the current object by reference. It’s as if you’d written this:

window.PointerPressed([this](auto&&...)
{
    printf("%ls\n", m_value.c_str());
});

Of course, that capture is just a raw pointer and knows nothing of reference-counting. We can capture a strong reference as follows:

window.PointerPressed([this, strong = get_strong()](auto&&...)
{
    printf("%ls\n", m_value.c_str());
});

And you might even want to exclude the automatic capture of the current object as follows:

window.PointerPressed([strong = get_strong()](auto&&...)
{
    printf("%ls\n", strong->m_value.c_str());
});

Notice that the variable is now accessed through the strong capture variable. Alternatively, you can also capture a weak reference:

window.PointerPressed([weak = get_weak()](auto&&...)
{
    if (auto strong = weak.get())
    {
        printf("%ls\n", strong->m_value.c_str());
    }
});

Some folks prefer member functions over lambdas. That works just as well, but of course the syntax for member functions is slightly different. Here’s the potentially dangerous member function handler using a raw object pointer:

struct App : implements<App, IInspectable>
{
    hstring m_value{ L"Hello world" };

    void Register(Window& window)
    {
        window.PointerPressed({ this, &App::Handler });
    }

    void Handler(IInspectable const&, int)
    {
        printf("%ls\n", m_value.c_str());
    }
};

This is just the standard or conventional way to refer to an object and a corresponding member function. Of course, there’s no use calling get_strong or get_weak from within the handler as we did previously with the coroutine. It may well be too late, as the app object may already have been destroyed by the time the event is raised and the handler is caller. Instead, the choice of weak or strong reference must be established at the point at which the handler is registered, and the app object is still known to be alive. Fortunately, the get_strong and get_weak functions can also be applied here. Consider again the event registration:

window.PointerPressed({ this, &App::Handler });

We can use the get_strong function in place of the raw this pointer as follows:

window.PointerPressed({ get_strong(), &App::Handler });

C++/WinRT ensures that the resulting delegate will hold a strong reference to the current object so that the handler can access any member variables without any concern. Similarly, the get_weak function may be used as follows:

window.PointerPressed({ get_weak(), &App::Handler });

In this case, C++/WinRT ensures that the resulting delegate holds a weak reference. This delegate will internally attempt to resolve it to a strong reference at the last minute and will only call the member function if a strong reference is acquired.

And that’s all I have for today. I trust you will find that C++/WinRT provides a great deal of flexibility when dealing with more complex lifetime management scenarios.

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