The Linux Page

Is it safe to use a shared_ptr<>() to track my resource data?

Water Leaking in a Bucket

shared_ptr<>() and Leaked Resource on Exception

If you used C++ for a while, you may have come across a note saying that the shared_ptr<>() was not (always) safe to use with tracking resource.

The fact is that there is a trick to using a shared pointer.

The following code will look correct to most of us, after all, you create a resource then save it in a shared pointer which is going to automatically get rid of it on exceptions or when you return from your function. Great!

#include <memory>


void deleter(FILE * f)
{
    fclose(f);
}



...
FILE * f(fopen("/tmp/random", O_CREAT | ...));
if(f == nullptr)
{
    ...handle error...
}

// here f is a potential leak liability

std::shared_ptr<FILE, decltype(&deleter)> raii_file(f, deleter);

// now f will always be close(f)'d

The problem of shared_ptr<>() is that is allocates memory so a bad_alloc exception can occur. So in the code above, if that bad_alloc (or other exception) occurs while attempting to create the raii_file, you will leak that FILE object.

Here are two solutions.

Switch to unique_ptr<>()

In many cases, it will be easy enough to convert to a unique_ptr<>(). After all, that resolves the issue as is because a unique_ptr<>() constructor can't throw an exception (outside of a stack overflow, I suppose):

FILE * f(fopen("/tmp/random", O_CREAT | ...));
if(f == nullptr)
{
    ...handle error...
}
std::unique_ptr<FILE, decltype(&deleter)> raii_file(f, deleter);

There are major reasons why this often doesn't work.

You Pass the Pointer to Other Functions

If you have to share the pointer with other functions (except maybe internal lambdas), you'll want to use shared_ptr<>(). Keep in mind that you can't share a unique_ptr<>(). So if the f parameter is only used in your current function, you'll be just fine. If you have to share it all over the place, that's not going to be practical at all.

Remember that copying a resource from one unique_ptr<>() to another does a transfer of the pointer. In other words, the old unique_ptr<>() becomes a null pointer and the new one is responsible for deleting the resource.

You Return the Pointer

Another major reason for not using a unique_ptr<>() is because you end up returning the pointer. Again, the fact that the copy of a unique_ptr<>() doesn't do a copy but a transfer, it doesn't work well in a return statement.

Making the shared_ptr<>() Safe

So... is there a way to make my first example work safely?

Simple Objects

The problem we mentioned is that the constructor is going to allocate memory. This is actually try in all situations, for example, doing the following is not safe either:

std::shared_ptr<my_type_t> obj(new my_type_t);

Your expression between the parenthesis allocates an object.

Then the shared_ptr<>() attempts an allocation and that allocation fails. It throws with a bad_alloc. The object you just allocated is leaked.

For a simple allocation, the shared_ptr<>() implementation offers a simple fix. You can use the make_shared<>() instead. So the allocation above is changed to:

std::shared_ptr<my_type_t> obj(std::make_shared<my_type_t>());

When you do an allocation for a resource other than just a memory buffer for an object, the std::make_shared<>() can't be used.

Note: You can pass arguments to your my_type_t constructors. C++20 also adds the ability to allocate an array instead of just one instance of an object.

Generic Solution

The solution is to pre-create the shared pointer and then use the reset() function. This is because the reset() function knows to delete the object if the allocation fails (assuming an allocation is required.)

So the following code looks very similar to my first example, but it is safe in all cases:

#include <memory>

...
std::shared_ptr<FILE, decltype(&deleter)> raii_file;
FILE * f(fopen("/tmp/random", O_CREAT | ...));
if(f == nullptr)
{
    ...handle error...
}
raii_file.reset(f, deleter);

When the reset() needs to allocate a memory buffer and that somehow fails, the function automatically calls your deleter. So something like this happens:

reset(p)
{
    try
    {
        ptr = new some_class;  // allocation happens here
        ptr.count = 1;        // success!
        ptr.data = p;
    }
    catch(...)                // failure...
    {
        deleter(p);           // get rid of the resource
        throw;                // propagate the exception
    }
}

My example is extremely simplified. They use an allocator, the function first checks whether a some_class object already exists, etc. But it shows the principal which shows how the resource will safely be removed when the reset() throws.

More reading: RAII Generic Deleter