Providing a custom deleter to std::unique_ptr

When using std::unique_ptr you can provide a custom deleter that will be called when the object goes out of scope. This can be used for reasons other than freeing memory.


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

struct CloserStruct {
    void operator()(FILE* f) { fclose(f); }
};

int main() {
    std::unique_ptr log_func(fopen("file.log", "r"), closer_func);

    std::unique_ptr log_struct(fopen("file.log", "r"));

    auto closer_lambda = [](FILE* f) { fclose(f); };
    std::unique_ptr log_lambda(fopen("file.log", "r"), closer_lambda);
}

I prefer to use a lambda, as I can write it locally to the where it’ll be used, and don’t have to create a separate function, or class, whose purpose, or caller, might not be obvious without a comment.

Using a lambda to specify the deleter works fine in a function, but has several problems. Firstly, if I wanted log_lambda to be a class member, then I’d need my custom deleter to be a function, or class, instead of a lambda. Secondly, a lambda doesn’t have a default constructor, so the unique_ptr must store the lambda, effectively doubling its size. Finally, i’m forced to write closer_lambda twice – in the decltype and after the pointer argument. I’m not sure why it can’t deduce the type from the second argument.

C++14 adds std::make_unique, which is great as we now don’t need to write new anywhere. However, using this means that we have no way of specifying a custom deleter. Although std::make_unique takes arguments, these are passed to the object’s constructor, not to the unique_ptr.

auto log_fails = std::make_unique(fopen("file.log", "r"));
                                    //^decltype(closer_lambda) ^ closer_lambda

A solution to member, size, and extra typing problems appears to be by using a specialisation of std::default_delete. This is what is used if no custom deleter is used, and calls delete on the held pointer. It has a default constructor meaning it can be created when needed, rather than needing to be stored. The size of the unique_ptr is just that of the held pointer.


namespace std
{
template
struct default_delete {
    void operator()(FILE* f) { fclose(f); }
};
}

The C++ Standard explicitly forbids you from declaring your own constructs in the std namespace, but you are allowed to specialize templates. The following passage from the C++ Standard signals why the final solution is permissible.

“A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.” [n3337; 17.6.4.2.1/1]

Advertisements

2 comments

  1. It’s a nice idea to make a default_delete specialization for FILE, i’ll implement it in my code.

    There is a bug in your text:
    void operator()(FILE* ptr) { fclose(f);}

    change f to ptr ( or ptr to f )

    Like

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