¿Cómo evitar especificar un deleter para std::Shared PTR cada vez que construya o reinicie?

El problema que quiero resolver

std::unique_ptr tiene dos parámetros de plantilla, el segundo es el borrador a usar. Debido a este hecho, es fácil alias unique_ptr a un tipo que requiere un programa de eliminación personalizado (por ejemplo, SDL_Texture) de la siguiente manera:
using SDL_TexturePtr = unique_ptr<SDL_Texture, SDL2PtrDeleter>;
Y... Donde SDL2PtrDeleter es un functor que se utiliza como borrador.
Con este alias, los programadores pueden construir y reiniciar SDL_TexturePtr sin preocuparse ni siquiera saber acerca de los borradores personalizados:
SDL_TexturePtr ptexture(SDL_CreateTexture(/*args*/));

//...

ptexture.reset(SDL_CreateTexture(/*args*/));
Por otro lado, std::shared_ptr no tiene un parámetro de plantilla que permita especificar un program a de borrado como parte de un tipo, por lo que lo siguiente es ilegal:
// error: wrong number of template arguments (2, should be 1)
using SDL_TextureSharedPtr = shared_ptr<SDL_Texture, SDL2PtrDeleter>;
Por lo tanto, la mejor manera de usar un alias de tipo es:
using SDL_TextureSharedPtr = shared_ptr<SDL_Texture>;
Sin embargo, esto tiene pocas ventajas sobre el uso explícito de shared_ptr<SDL_Texture>, ya que el usuario debe conocer la función de borrado a utilizar y especificarla cada vez que construya o reinicie SDL_TextureSharedPtr:
SDL_TextureSharedPtr ptexture(SDL_CreateTexture(/*args*/), SDL_DestroyTexture);

//...

ptexture.reset(SDL_CreateTexture(/*args*/), SDL_DestroyTexture);
Como puede ver en el ejemplo anterior, el usuario necesita saber la función correcta para eliminar SDL_Texture (es decir, SDL_DestroyTexture()) y pasar un puntero a ella cada vez. Además de las molestias, esto hace que sea menos probable que los programadores introduzcan errores especificando funciones incorrectas como borradores.
Quiero encapsular el borrador de alguna manera en el tipo del puntero compartido en sí. Debido a que, por lo que sé, esto no se puede hacer usando sólo alias de tipo, consideré tres opciones:
  • crea una clase que envuelve std::shared_ptr<T>, que copia la interfaz de shared_ptr, pero permite especificar la función deleter a través de sus propios parámetros de plantilla. A continuación, cuando el constructor o el método operator() de su instancia subyacente reset() se llama desde su propio constructor o método std::shared_ptr<T>, respectivamente, el envoltorio proporciona un puntero a reset() de su instancia de borrador. Por supuesto, la desventaja es que toda la interfaz bastante grande de std::shared_ptr debe ser replicada en esta clase de envoltura, que está mojada.
  • crea subclases de std::shared_ptr<T> que permiten especificar la función deleter a través de sus propios parámetros de plantilla. Asumiendo la herencia public, esto nos ayudará a evitar la necesidad de copiar la interfaz shared_ptr, pero abrirá su propio gusano. Aunque std::shared_ptr no es final, no parece haber sido diseñado como una subclase porque tiene un destructor no virtual (aunque no es un problem a en este caso particular). Peor a ún, el método reset() en shared_ptr no es virtual y por lo tanto no puede ser reescrito - sólo oculto, lo que abre la puerta al uso incorrecto: a través de la herencia public, un usuario puede pasar una referencia a una instancia de subclase a una API que acepta std::shared_ptr<T>&, y su implementación puede llamar a reset() para eludir completamente nuestro método. Para la herencia no pública, obtenemos el mismo resultado que la opción 1.
  • Para las dos opciones anteriores, en última instancia, suponiendo que SDL_TextureSharedPtr es nuestra clase (sub), MySharedPtr<T, Deleter> puede ser representado como:
    using SDL_TextureSharedPtr = MySharedPtr<SDL_Texture, SDL2PtrDeleter>;
    
    La tercera opción, anteriormente aquí, se refiere a la especialización std::default_delete<T>. Esto se basa en mi suposición errónea de que std::shared_ptr<T> usará std::default_delete<T> como unique_ptr si el borrador no está explícitamente proporcionado. No es así. ¡Gracias @DieterLücking por señalar esto! Teniendo en cuenta estas opciones y el razonamiento anterior, mi pregunta es la siguiente.
    ¿Me he perdido una manera más fácil de evitar tener que especificar un borrador cada vez que construya una instancia std::shared_ptr<T> o reset()?
    ¿En caso negativo, es correcto el razonamiento de las opciones que he enumerado? ¿Hay alguna otra razón objetiva para preferir una de estas opciones a la otra?

    Solución correspondiente

    using SDL_TexturePtr = unique_ptr<SDL_Texture, SDL2PtrDeleter>;
    

    Given this alias, programmers are able to construct and reset SDL_TexturePtr without caring or even knowing about custom deleter:


    Es una simplificación excesiva (generalmente fatal). Por el contrario, si el borrador construido por defecto es adecuado para la construcción, el valor actual del borrador es adecuado para reiniciar el puntero sin cambios manuales.
    Usted tiene razón sobre las desventajas de envolver o extender shared_ptr, aunque algunos podrían decir que le permite añadir nuevos métodos de instancia.
    Sin embargo, usted debe minimizar el acoplamiento, lo que significa que prefiere funciones libres, ya que sólo se requieren interfaces comunes existentes para escribirlas.
    Si no especifica que el borrador resultará en el uso de std::default_delete (desafortunadamente no lo hace), y sólo se necesita un borrador por tipo, o si la expresión de borrado estándar se ajustará a su caso de uso (no parece encajar), entonces la tercera opción será la mejor que pueda elegir.
    Por lo tanto, hay una opción diferente:
    Use constructores para abstraer (y puede ser complicado) construcciones y borradores personalizados.
    Esto le permite escribir sólo una vez, y el uso gratuito de auto puede reducir aún más su dolor de cabeza.