web-dev-qa-db-fra.com

Comment utiliser un deleter personnalisé avec un membre std :: unique_ptr?

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?

117
huitlarc

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.

115
Cassio Neri

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.

106
Drew Noakes

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.

56
rici

À 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()};
12
Justin

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);});
4
mkaes

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 };
}
_
4
Deduplicator