Typedef - a shared_ptr type with a static custom deleter similar to unique_ptr
I've read a lot of SO questions on custom delimiter for shared_ptr
and unique_ptr
, and the difference between the two. But I still haven't found a clear answer to this question:
What is the best way to create a type that acts like shared_ptr
a custom deletion, similar to unique_ptr
having a deleter as part of a type definition?
In use, unique_ptr
I am using a deleter class that handles the deletion of individual types (for brevity, limiting it to only two types):
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.
All is well and good. However, how do you achieve the same result for shared_ptr
? Its type is defined as
template< class T > class shared_ptr;
and the way to provide custom delete is through the constructor. It is not true that the shell user shared_ptr
needs to know what type of pointer is wrapped and how that pointer should be removed. What would be the best way to achieve the same usage as with the above example unique_ptr
.
In other words, I could end up with:
SurfaceShPtr surface(IMG_Load("image.png"));
Instead of something like
SurfaceShPtr surface(IMG_Load("image.png"),
[=](SDL_Surface* ptr){SDL_FreeSurface(ptr);});
Or, a little better
SurfaceShPtr surface(IMG_Load("image.png"),
SDL_Deleter());
Is there a way to do this without creating a RAII wrapper class (instead of a typedef), adding even more overhead?
If the answer is "it's impossible". Why not?
source to share
Another answer given here was that something close to what I asked for could be accomplished via function returns unique_ptr
with a custom deleter that could be implicitly converted to shared_ptr
.
The answer given was that a deterter defined as a type trait was not possible for std::shared_ptr
. The alternative suggested answer is to use a function that returns unique_ptr
implicitly converted to shared_ptr
.
Since this is not part of the type, it is possible to make a simple mistake that will leak memory. Which I wanted to avoid.
For example:
// Correct usage:
shared_ptr<SDL_Surface> s(createSurface(IMG_Load("image.png")));
// Memory Leak:
shared_ptr<SDL_Surface> s(IMG_Load("image.png"));
The concept I want to express is having a deleter as part of the type (which unique_ptr
allows), but with functionality shared_ptr
. My suggested solution comes from shared_ptr
and provides the type of the deletion as a template argument. This does not require additional memory and works the same as for unique_ptr
.
template<class T, class D = std::default_delete<T>>
struct shared_ptr_with_deleter : public std::shared_ptr<T>
{
explicit shared_ptr_with_deleter(T* t = nullptr)
: std::shared_ptr<T>(t, D()) {}
// Reset function, as it also needs to properly set the deleter.
void reset(T* t = nullptr) { std::shared_ptr<T>::reset(t, D()); }
};
Along with the deleter class (Thanks Jonathan Wackeli. The path is cleaner than my macro (now removed)):
struct SDL_Deleter
{
void operator()(SDL_Surface* p) const { if (p) SDL_FreeSurface(p); }
void operator()(SDL_RWops* p) const { if (p) SDL_RWclose(p); }
};
using SurfacePtr = std::unique_ptr<SDL_Surface, SDL_Deleter>;
using SurfaceShPtr = shared_ptr_with_deleter<SDL_Surface, SDL_Deleter>;
using RWopsPtr = std::unique_ptr<SDL_RWops, SDL_Deleter>;
using RWopsShPtr = shared_ptr_with_deleter<SDL_RWops, SDL_Deleter>;
Member instances SurfaceShPtr
will guarantee correct type cleanup, just like for SurfacePtr
what I wanted.
// Correct Usage (much harder to use incorrectly now):
SurfaceShPtr s(IMG_Load("image.png"));
// Still correct usage
s.reset(IMG_Load("other.png"));
I'll leave that for a while, for comments, etc., without accepting the answer. There are perhaps even more dangerous caveats that I missed (having a non-virtual destructor for more than one, since the parent shared_ptr
gets paid to delete).
source to share
typedef is a compile-time static function.
The check out passed in shared_ptr
is dynamic, runtime. The deleter is "erased" and is not included in the interface shared_ptr
.
Therefore, you cannot declare a typedef to represent an alternate debater, you just pass it to the constructor.
What would be the best way to achieve the same usage as with the above example
unique_ptr
.
You can use functions to create resources and return them in shared_ptr
shared_ptr<SDL_Surface> create_sdl_surface(const char* s)
{
return shared_ptr<SDL_Surface>(IMG_load(s), SDL_FreeSurface);
}
But I would like these functions to return unique_ptr
instead, which can be converted to shared_ptr
as shown below.
I would get rid of the macro and do something like this:
// type with overloaded functions for freeing each resource type
struct SDL_deleter
{
void operator()(SDL_Surface* p) const { if (p) SDL_FreeSurface(p); }
void operator()(SDL_RWops* p) const { if (p) SDL_RWclose(p); }
// etc.
};
// a unique_ptr using SDL_deleter:
template<typename P>
using SDL_Ptr = std::unique_ptr<P, SDL_deleter>;
// typedefs for the common ptr types:
using SurfacePtr = SDL_ptr<SDL_Surface>;
using RWopsPtr = SDL_ptr<SDL_RWops>;
// etc.
To answer the shared_ptr part of your question, define functions that create resources and return them in SDL_ptr
:
SurfacePtr createSurface(const char* s) { return SurfacePtr(IMG_load(s)); }
RWopsPtr createRWops([...]) { return RWopsPtr([...]); }
// etc.
Then you can easily create shared_ptr
from the result of these functions:
shared_ptr<SDL_Surface> s = createSurface("image.png");
shared_ptr
automatically gets the right deleter from unique_ptr
.
source to share