Avoiding memory leaks: part 2

Spread the love

In the previous post I introduced some of the common cases for avoiding memory leaks with judicious use of unique_ptr. Of course, not all memory management situations are as straightforward, especially when dealing with low-level “C-style” APIs. Let’s explore a few more examples.

Release ownership to legacy API

To release ownership of an object to code that doesn’t understand unique_ptr, you can use the release method. This ensures that at least your usage of the object is exception-safe up to the point you relinquish it.

void LegacyCodeToTakeResponsibility(MyClass * p)
{
    FuncTrace t(__FUNCTIONW__);
    wcout << L"I take responsibility for " << p->get_Name() << endl;
    // ... sometime later ...
    delete p;
}

void ReleaseOwnership()
{
    FuncTrace t(__FUNCTIONW__);
    MyClassUPtr p = make_unique<MyClass>(L"to-be-released");
    // ... do some work here that might cause an exception ...

    // Okay, now it's safe to give this away.
    LegacyCodeToTakeResponsibility(p.release());
    if (!p)
    {
        wcout << L"I can't use this instance anymore." << endl;
    }
}

int wmain(int argc, wchar_t ** argv)
{
    ReleaseOwnership();
    wcout << L"Final count: " << Counter::N << endl;
    return 0;
}

The output shows that the object is only released once and that the unique_ptr no longer refers to any valid instance:

{ Enter ReleaseOwnership
(Dynamic allocation)
MyClass:to-be-released
{ Enter LegacyCodeToTakeResponsibility
I take responsibility for to-be-released
~MyClass:to-be-released
(Dynamic deallocation)
} Leave LegacyCodeToTakeResponsibility
I can't use this instance anymore.
} Leave ReleaseOwnership
Final count: 0

Use custom deleter

Not all objects can be freed using delete. This is particularly true of resources that you did not explicitly allocate. In this example, a custom deleter is defined (as a template class which overloads the function call operator) which can free a buffer allocated by FormatMessage using LocalFree as documented by the API:

#include <Windows.h>
// ...

template <typename T>
struct LocalFreeDelete
{
    void operator()(T * ptr) const
    {
        wcout << L"LocalFree" << endl;
        LocalFree(ptr);
        --Counter::N;
    }
};

void FormatSomeMessage()
{
    FuncTrace t(__FUNCTIONW__);
    wchar_t const * message = L"Name: %1 / City: %2";
    wchar_t const * name = L"Xyz";
    wchar_t const * city = L"Abc";

    DWORD_PTR args[] = { (DWORD_PTR)name, (DWORD_PTR)city };

    LPWSTR rawBuffer = nullptr;
    DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY;
    DWORD result = FormatMessage(flags, message, 0, 0, (LPWSTR)&rawBuffer, 64, (va_list *)args);
    if (result != 0)
    {
        // Immediately wrap buffer to ensure it is safely freed
        unique_ptr<WCHAR, LocalFreeDelete<WCHAR>> p(rawBuffer);
        ++Counter::N;
        wcout << L"Formatted string: " << p.get() << endl;
        // ... now do something with the buffer ...
    }
}

int wmain(int argc, wchar_t ** argv)
{
    FormatSomeMessage();
    wcout << L"Final count: " << Counter::N << endl;
    return 0;
}

The output shows that we have indeed freed the buffer on exit of the scope:

{ Enter FormatSomeMessage
Formatted string: Name: Xyz / City: Abc
LocalFree
} Leave FormatSomeMessage
Final count: 0

Multiple owner ref-counting

Alas, not every object is uniquely owned. Just as success has a thousand fathers, some objects have more than one logical parent. To avoid orphaning the memory, shared_ptr is the tool for this situation. It internally maintains a reference count to ensure that the final instance that goes out of scope will destroy the object. Here is an example which uses the PPL:

#include <Windows.h>
#include <sstream>
#include <vector>
#include <ppltasks.h>
#include <concurrent_queue.h>

using namespace std;
using namespace concurrency;

// ...
void StartParallelTasks(vector<task<void>> & tasks, concurrent_queue<wstring> & queue)
{
    FuncTrace t(__FUNCTIONW__);
    MyClassSPtr sp = make_shared<MyClass>(L"shared-obj");
    for (int i = 0; i < 5; ++i)
    {
        task<void> task = create_task([sp, &queue]()
        {
            Sleep(500);
            DWORD id = GetCurrentThreadId();
            wstringstream wss;
            wss << L"T:" << id << L":" << sp->get_Name();
            queue.push(wss.str());
        });
        tasks.push_back(task);
    }
}

void StartAndWaitForTasks()
{
    FuncTrace t(__FUNCTIONW__);
    vector<task<void>> tasks;
    concurrent_queue<wstring> queue;
    StartParallelTasks(tasks, queue);

    task<void> allTasksCompleted = when_all(tasks.begin(), tasks.end()).then([&queue]()
    {
        for_each(queue.unsafe_begin(), queue.unsafe_end(), [](wstring & s)
        {
            wcout << s << endl;
        });
    });

    allTasksCompleted.wait();
}

int wmain(int argc, wchar_t ** argv)
{
    StartAndWaitForTasks();
    wcout << L"Final count: " << Counter::N << endl;
    return 0;
}

The output shows that even though the initial shared_ptr instance is long gone, the copies which were passed to the task lambda (via the capture clause) keep the object alive until the last task finishes:

{ Enter StartAndWaitForTasks
{ Enter StartParallelTasks
MyClass:shared-obj
} Leave StartParallelTasks
T:27200:shared-obj
~MyClass:shared-obj
T:34344:shared-obj
T:39640:shared-obj
T:40036:shared-obj
T:40372:shared-obj
} Leave StartAndWaitForTasks
Final count: 0

Passing an object to a background thread

Okay, so let’s say you’re not using the PPL, but explicit threads instead. In this case, using std::thread and shared_ptr is the way to go:

#include <Windows.h>
#include <thread>

// ...
thread CreateMyThread()
{
    FuncTrace t(__FUNCTIONW__);
    MyClassSPtr sp = make_shared<MyClass>(L"used-by-thread");
    return thread([](MyClassSPtr sp)
    {
        Sleep(1000);
        DWORD id = GetCurrentThreadId();
        wcout << L"Thread " << id << L" using " << sp->get_Name() << endl;
    }, sp);
}

void CreateAndWaitForMyThread()
{
    FuncTrace t(__FUNCTIONW__);
    thread myThread = CreateMyThread();
    myThread.join();
}

int wmain(int argc, wchar_t ** argv)
{
    CreateAndWaitForMyThread();
    wcout << L"Final count: " << Counter::N << endl;
    return 0;
}

The output shows a similar situation as in the previous example where the object goes out of scope and is deleted shortly after the thread ends:

{ Enter CreateAndWaitForMyThread
{ Enter CreateMyThread
MyClass:used-by-thread
} Leave CreateMyThread
Thread 41764 using used-by-thread
~MyClass:used-by-thread
} Leave CreateAndWaitForMyThread
Final count: 0

Passing an object to a background thread (legacy)

Fine, you don’t want to use std::thread. You can modify the above sample to use the raw Win32 calls (e.g. CreateThread, WaitForSingleObject, CloseHandle) but it is somewhat more error prone. Basically, you need to ensure cleanup happens inside the thread on success or before you return/throw on failure.

#include <Windows.h>

// ...
DWORD WINAPI MyLegacyThreadFunc(void * obj)
{
    MyClassUPtr p(static_cast<MyClass *>(obj));
    Sleep(1000);
    DWORD id = GetCurrentThreadId();
    wcout << L"Thread " << id << L" using " << p->get_Name() << endl;
    return 0;
}

HANDLE CreateMyLegacyThread()
{
    FuncTrace t(__FUNCTIONW__);
    MyClassUPtr p = make_unique<MyClass>(L"used-by-thread");
    HANDLE handle = CreateThread(nullptr, 0, MyLegacyThreadFunc, p.get(), 0, nullptr);
    if (!handle)
    {
        throw runtime_error("Failed!");
    }

    // Must release to avoid double-delete!
    p.release();
    return handle;
}

void CreateAndWaitForMyLegacyThread()
{
    FuncTrace t(__FUNCTIONW__);
    HANDLE myThread = CreateMyLegacyThread();
    WaitForSingleObject(myThread, INFINITE);
    CloseHandle(myThread);
}

int wmain(int argc, wchar_t ** argv)
{
    CreateAndWaitForMyLegacyThread();
    wcout << L"Final count: " << Counter::N << endl;
    return 0;
}

As expected, the thread will clean up the resource if it starts properly:

{ Enter CreateAndWaitForMyLegacyThread
{ Enter CreateMyLegacyThread
(Dynamic allocation)
MyClass:used-by-thread
} Leave CreateMyLegacyThread
Thread 33656 using used-by-thread
~MyClass:used-by-thread
(Dynamic deallocation)
} Leave CreateAndWaitForMyLegacyThread
Final count: 0

That should be enough examples to give you an idea of smart ways to manage memory (or really, object ownership) in C++. Adoption of these kinds of modern coding practices should ensure safer, cleaner, less leaky applications.

Leave a Reply

Your email address will not be published. Required fields are marked *