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

后端 未结 2 967
萌比男神i
萌比男神i 2020-12-19 19:26

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

相关标签:
2条回答
  • 2020-12-19 19:50

    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.

    0 讨论(0)
  • 2020-12-19 20:09

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

    0 讨论(0)
提交回复
热议问题