Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
280 views
in Technique[技术] by (71.8m points)

c++ - Typedef a shared_ptr type with a static custom deleter, similar to unique_ptr

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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 a shared_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 a unique_ptr, implicitly converted to a shared_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:

// 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 the deleter as part of the type (which unique_ptr allows), but with the functionality of a shared_ptr. My suggested solution is deriving from shared_ptr, and providing the deleter type as a template argument. This takes up no additional memory, and works in the same way 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());  }
};

Together with a deleter class (Thanks Jonathan Wakely. Way 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>;

Instances with SurfaceShPtr members are type guaranteed to clean up properly, the same as for SurfacePtr, which is 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 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).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...