¿Cómo uso un eliminador personalizado con un miembro std :: unique_ptr?

Tengo una clase con un miembro unique_ptr.

class Foo { private: std::unique_ptr bar; ... }; 

La barra es una clase de terceros que tiene una función create () y una función destroy ().

Si quisiera usar un std::unique_ptr con él en una función independiente, podría hacerlo:

 void foo() { std::unique_ptr bar(create(), [](Bar* b){ destroy(b); }); ... } 

¿Hay alguna manera de hacer esto con std::unique_ptr como miembro de una clase?

Asumiendo que create y destroy son funciones gratuitas (lo que parece ser el caso del fragmento de código del PO) con las siguientes firmas:

 Bar* create(); void destroy(Bar*); 

Puedes escribir tu clase Foo así

 class Foo { std::unique_ptr ptr_; // ... public: Foo() : ptr_(create(), destroy) { /* ... */ } // ... }; 

Tenga en cuenta que no necesita escribir ningún lambda o eliminador personalizado aquí porque destroy ya es un eliminador.

Es posible hacer esto limpiamente usando un lambda en C ++ 11 (probado en G ++ 4.8.2).

Dado este typedef reutilizable:

 template using deleted_unique_ptr = std::unique_ptr>; 

Puedes escribir:

 deleted_unique_ptr foo(new Foo(), [](Foo* f) { customdeleter(f); }); 

Por ejemplo, con un FILE* :

 deleted_unique_ptr file( fopen("file.txt", "r"), [](FILE* f) { fclose(f); }); 

Con esto obtienes los beneficios de la limpieza segura de excepciones usando RAII, sin necesidad de probar / atrapar ruido.

Solo necesitas crear una clase de eliminador:

 struct BarDeleter { void operator()(Bar* b) { destroy(b); } }; 

y proporcionarlo como el argumento de la plantilla de unique_ptr . Aún tendrá que inicializar unique_ptr en sus constructores:

 class Foo { public: Foo() : bar(create()), ... { ... } private: std::unique_ptr bar; ... }; 

Hasta donde yo sé, todas las bibliotecas c ++ populares implementan esto correctamente; ya que BarDeleter realidad no tiene ningún estado, no necesita ocupar ningún espacio en el unique_ptr .

Ya sabes, usar un eliminador personalizado no es la mejor manera de hacerlo, ya que tendrás que mencionarlo en todo tu código.
En cambio, como se le permite agregar especializaciones a las clases de nivel de espacio en ::std , siempre y cuando estén involucrados los tipos personalizados y respete la semántica, haga eso:

Especializar std::default_delete :

 template <> struct ::std::default_delete { default_delete() = default; template ()>> constexpr default_delete(default_delete) noexcept {} void operator()(Bar* p) const noexcept { destroy(p); } }; 

Y tal vez también haga std::make_unique() :

 template <> inline ::std::unique_ptr ::std::make_unique() { auto p = create(); if (!p) throw std::runtime_error("Could not `create()` a new `Bar`."); return { p }; } 

Simplemente puede usar std::bind con una función de destrucción.

 std::unique_ptr> bar(create(), std::bind(&destroy, std::placeholders::_1)); 

Pero por supuesto también puedes usar una lambda.

 std::unique_ptr> ptr(create(), [](Bar* b){ destroy(b);}); 

A menos que necesite poder cambiar el eliminador en el tiempo de ejecución, le recomiendo usar un tipo de eliminador personalizado. Por ejemplo, si usa un puntero de función para su eliminador, sizeof(unique_ptr) == 2 * sizeof(T*) . En otras palabras, la mitad de los bytes del objeto unique_ptr se desperdicia.

Sin embargo, escribir un borrador personalizado para ajustar cada función es una molestia. Afortunadamente, podemos escribir un tipo de plantilla en la función:

Desde C ++ 17:

 template  using deleter_from_fn = std::integral_constant; template  using my_unique_ptr = std::unique_ptr>; // usage: my_unique_ptr p{create()}; 

Antes de C ++ 17:

 template  using deleter_from_fn = std::integral_constant; template  using my_unique_ptr = std::unique_ptr>; // usage: my_unique_ptr p{create()};