I have read through many questions on SO on custom deleter for shared_ptr
and unique_ptr
, and the difference between the two. But, I still haven't found any clear answer to this question:
How can one best go about creating a type that acts as a shared_ptr
with a custom deleter, similar to how unique_ptr
has the deleter as part of the type definition?
For unique_ptr
usage, I use a deleter class, that handles deletion of individual types (limiting it to just two types, for brevity):
struct SDL_Deleter {
void operator()( SDL_Surface* ptr ) { if (ptr) SDL_FreeSurface( ptr );}
void operator()( SDL_RWops* ptr ) { if (ptr) SDL_RWclose( ptr );}
};
using SurfacePtr = std::unique_ptr<SDL_Surface, SDL_Deleter>;
using RWopsPtr = std::unique_ptr<SDL_RWops, SDL_Deleter>;
Which can be used with something like
SurfacePtr surface(IMG_Load("image.png"));
And will call SDL_FreeSurface
upon destruction.
This is all fine and well. However, how does one go about achieving the same for shared_ptr
? Its type is defined as
template< class T > class shared_ptr;
and the way to provide a custom deleter is through the constructor. It doesn't feel right that the user of the shared_ptr
wrapper needs to know which pointer type is wrapped, and how that pointer is supposed to be deleted. What would be the best way to achieve the same kind of usage as with the unique_ptr
example of above.
In other words, that I could end up with:
SurfaceShPtr surface(IMG_Load("image.png"));
Instead of of something like
SurfaceShPtr surface(IMG_Load("image.png"),
[=](SDL_Surface* ptr){SDL_FreeSurface(ptr);});
Or, just slightly better
SurfaceShPtr surface(IMG_Load("image.png"),
SDL_Deleter());
Is there a way to do this, without having to create a RAII wrapper class (instead of a typedef), adding even more overhead?
If the answer is "this isn't possible". Why not?
A typedef is a static, compile-time feature.
A deleter passed to a
shared_ptr
is a dynamic, run-time property. The deleter is "type-erased" and is not part of theshared_ptr
interface.Therefore you can't declare a typedef to represent an alternative deleter, you just pass one to the constructor.
You could use functions to create the resources and return them in a
shared_ptr
But I would have those functions return a
unique_ptr
instead, which can be converted toshared_ptr
, as below.I would get rid of the macro and do something like this:
To answer the shared_ptr part of your question, define functions that create resources and return them in a
SDL_ptr
:Then you can easily create a
shared_ptr
from the result of those functions:The
shared_ptr
automatically acquires the right deleter from theunique_ptr
.The other answer provided here was that something close to what I asked could be done through function returns of
unique_ptr
with custom deleter, which can be implicitly converted to ashared_ptr
.The answer given was that a deleter defined as a type trait was not possible for
std::shared_ptr
. The answer suggested as an alternative, to use a function which returns aunique_ptr
, implicitly converted to ashared_ptr
.Since this isn't part of the type, it is possible to make a simple mistake, leading to memory leaks. Which is what I wanted to avoid.
For example:
The concept I want to express is having the deleter as part of the type (which
unique_ptr
allows), but with the functionality of ashared_ptr
. My suggested solution is deriving fromshared_ptr
, and providing the deleter type as a template argument. This takes up no additional memory, and works in the same way as forunique_ptr
.Together with a deleter class (Thanks Jonathan Wakely. Way cleaner than my macro (now removed)):
Instances with
SurfaceShPtr
members are type guaranteed to clean up properly, the same as forSurfacePtr
, which is what I wanted.I'll leave this up for a while, for comments, etc, without accepting the answer. Maybe there are even more dangerous caveats I've missed (having a non-virtual destructor not being one, as the parent
shared_ptr
is given charge of the deletion).