简体   繁体   中英

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?

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

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 the shared_ptr interface.

Therefore you can't declare a typedef to represent an alternative deleter, you just pass one to the constructor.

What would be the best way to achieve the same kind of usage as with the unique_ptr example of above.

You could use functions to create the resources and return them in a 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 have those functions return a unique_ptr instead, which can be converted to shared_ptr , as 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 a SDL_ptr :

SurfacePtr createSurface(const char* s) { return SurfacePtr(IMG_load(s)); }
RWopsPtr createRWops([...]) { return RWopsPtr([...]); }
// etc.

Then you can easily create a shared_ptr from the result of those functions:

shared_ptr<SDL_Surface> s = createSurface("image.png");

The shared_ptr automatically acquires the right deleter from the unique_ptr .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM