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?

+3


source to share


2 answers


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).

+5


source


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

.

+2


source







All Articles