J'ai une classe avec un membre unique_ptr.
class Foo {
private:
std::unique_ptr<Bar> bar;
...
};
La barre est une classe tierce dotée d'une fonction create () et d'une fonction destroy ().
Si je voulais utiliser un std::unique_ptr
avec cela dans une fonction autonome, je pourrais faire:
void foo() {
std::unique_ptr<Bar, void(*)(Bar*)> bar(create(), [](Bar* b){ destroy(b); });
...
}
Y a-t-il un moyen de faire cela avec std::unique_ptr
en tant que membre d'une classe?
En supposant que create
et destroy
sont des fonctions libres (ce qui semble être le cas de l'extrait de code du PO) avec les signatures suivantes:
Bar* create();
void destroy(Bar*);
Vous pouvez écrire votre classe Foo
comme ceci
class Foo {
std::unique_ptr<Bar, void(*)(Bar*)> ptr_;
// ...
public:
Foo() : ptr_(create(), destroy) { /* ... */ }
// ...
};
Notez que vous n’avez pas besoin d’écrire un suppresseur lambda ou personnalisé ici parce que destroy
est déjà un déléteur.
Il est possible de le faire proprement en utilisant un lambda en C++ 11 (testé en G ++ 4.8.2).
Étant donné ce typedef
réutilisable:
template<typename T>
using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;
Tu peux écrire:
deleted_unique_ptr<Foo> foo(new Foo(), [](Foo* f) { customdeleter(f); });
Par exemple, avec un FILE*
:
deleted_unique_ptr<FILE> file(
fopen("file.txt", "r"),
[](FILE* f) { fclose(f); });
Avec cela, vous bénéficiez des avantages d'un nettoyage sans risque avec RAII, sans avoir besoin d'essayer/d'attraper du bruit.
Il vous suffit de créer une classe deleter:
struct BarDeleter {
void operator()(Bar* b) { destroy(b); }
};
et le fournir comme argument de template de unique_ptr
. Vous devrez toujours initialiser unique_ptr dans vos constructeurs:
class Foo {
public:
Foo() : bar(create()), ... { ... }
private:
std::unique_ptr<Bar, BarDeleter> bar;
...
};
Autant que je sache, toutes les bibliothèques populaires c ++ l'implémentent correctement; puisque BarDeleter
n'a pas réellement d'état, il n'a pas besoin d'occuper d'espace dans le unique_ptr
.
À moins que vous n'ayez besoin de pouvoir modifier le programme de suppression au moment de l'exécution, je vous recommande fortement d'utiliser un type de programme de suppression personnalisé. Par exemple, si vous utilisez un pointeur de fonction pour votre suppression, sizeof(unique_ptr<T, fptr>) == 2 * sizeof(T*)
. En d'autres termes, la moitié des octets de l'objet unique_ptr
est perdue.
Écrire un suppresseur personnalisé pour envelopper toutes les fonctions est un problème, cependant. Heureusement, nous pouvons écrire un type basé sur la fonction:
Depuis C++ 17:
template <auto fn>
using deleter_from_fn = std::integral_constant<decltype(fn), fn>;
template <typename T, auto fn>
using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;
// usage:
my_unique_ptr<Bar, destroy> p{create()};
Avant C++ 17:
template <typename D, D fn>
using deleter_from_fn = std::integral_constant<D, fn>;
template <typename T, typename D, D fn>
using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<D, fn>>;
// usage:
my_unique_ptr<Bar, decltype(destroy), destroy> p{create()};
Vous pouvez simplement utiliser std::bind
avec une fonction de destruction.
std::unique_ptr<Bar, std::function<void(Bar*)>> bar(create(), std::bind(&destroy,
std::placeholders::_1));
Mais bien sûr, vous pouvez également utiliser un lambda.
std::unique_ptr<Bar, std::function<void(Bar*)>> ptr(create(), [](Bar* b){ destroy(b);});
Vous savez, utiliser un deleter personnalisé n'est pas la meilleure solution, car vous devrez le mentionner dans votre code.
À la place, puisque vous êtes autorisé à ajouter des spécialisations aux classes d'espaces de nommage dans _::std
_ tant que des types personnalisés sont impliqués et que vous respectez la sémantique, procédez comme suit:
Spécialiser std::default_delete
:
_template <>
struct ::std::default_delete<Bar> {
default_delete() = default;
template <class U, class = std::enable_if_t<std::is_convertible<U*, Bar*>()>>
constexpr default_delete(default_delete<U>) noexcept {}
void operator()(Bar* p) const noexcept { destroy(p); }
};
_
Et peut-être aussi faire std::make_unique()
:
_template <>
inline ::std::unique_ptr<Bar> ::std::make_unique<Bar>() {
auto p = create();
if (!p) throw std::runtime_error("Could not `create()` a new `Bar`.");
return { p };
}
_