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]
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 )
LikeLike
Nice technique … used it in stackoverflow answer:
https://stackoverflow.com/questions/11886262/reading-public-private-key-from-memory-with-openssl/53608170#53608170
LikeLike