std::unique_ptr has 2 template parameters, the second of which is the used deleter. Because of this, you can easily assign a unique_ptr type to a type that requires user deletion (for example, SDL_Texture ) as follows:
using SDL_TexturePtr = unique_ptr<SDL_Texture, SDL2PtrDeleter>;
... where SDL2PtrDeleter is a functor to be used as deleter.
Given this alias, programmers can create and reset SDL_TexturePtr without concern or even know about user deletion:
SDL_TexturePtr ptexture(SDL_CreateTexture());
std::shared_ptr , on the other hand, does not have a template parameter that allows you to specify a deleter as part of this type, so the following is illegal:
So, the best thing to do with an alias like:
using SDL_TextureSharedPtr = shared_ptr<SDL_Texture>;
But this has several advantages over using shared_ptr<SDL_Texture> explicitly, since the user must know the function of the deleter to use and indicate it every time they are created or reset a SDL_TextureSharedPtr in any case:
SDL_TextureSharedPtr ptexture(SDL_CreateTexture(), SDL_DestroyTexture);
As you can see from the above example, the user needs to know the correct function to delete SDL_Texture (which is SDL_DestroyTexture() ) and pass a pointer to it each time. In addition to being inconvenient, this creates a small chance that the programmer may introduce an error by specifying the wrong function as a deleter.
I would like to somehow encapsulate the deleter in the type of the most common pointer. Since, as far as I see, there is no way to achieve this, simply using a type alias, I considered 3 options:
Create a class, wrapping std::shared_ptr<T> , which duplicates the shared_ptr interface, but allows you to specify the delete functor through its own template parameter. Then this shell will provide a pointer to its child instance operator() when calling the constructor method or reset() its base instance std::shared_ptr<T> from its own constructor or reset() method, respectively. The disadvantage, of course, is that the entire, fairly significant interface std::shared_ptr must be duplicated in this packaging class, which is WET.
Create a subclass of std::shared_ptr<T> that allows the deleteter functor to be specified through its own template parameter. This, assuming public inheritance, will help us avoid the need to duplicate the shared_ptr interface, but it will open the bank of our own worms. Although std::shared_ptr not final , it did not seem to be intended for a subclass, since it has a non-virtual destructor (although this is not a problem in this particular case). To make matters worse, the reset() method in shared_ptr not virtual and therefore cannot be overridden - it is only obscured, which opens the door for misuse: with public inheritance, users can pass a link to an instance of our subclass to some API by accepting std::shared_ptr<T>& , the implementation of which can call reset() , completely bypassing our method. With unspoken inheritance, we get the same thing as with option # 1.
In both of the above options, SDL_TextureSharedPtr can be expressed as follows: it is assumed that MySharedPtr<T, Deleter> is our (sub) class:
using SDL_TextureSharedPtr = MySharedPtr<SDL_Texture, SDL2PtrDeleter>;
- The third option was here, and it included the specialization
std::default_delete<T> . This was based on my incorrect assumption that std::shared_ptr<T> uses std::default_delete<T> , for example, unique_ptr if no failure was explicitly provided. This is not the case. Thanks @DieterLücking for pointing this out!
Given these options and the reasoning above, here is my question.
Did I skip the simpler way to avoid specifying a delete for std::shared_ptr<T> with every instance of it or reset() ?
If this is not the case, are my arguments correct for the options I listed? Are there other objective reasons to prefer one of these options over the other?