172 votes

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

J'ai une classe avec un membre unique_ptr.

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

Bar est une classe tierce qui a une fonction create() et 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(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?

162voto

Cassio Neri Points 6095

En supposant que create et destroy sont des fonctions libres (comme le semble être le cas dans le extrait de code de l'OP) avec les signatures suivantes :

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

Vous pouvez écrire votre classe Foo comme ceci

class Foo {

    std::unique_ptr ptr_;

    // ...

public:

    Foo() : ptr_(create(), destroy) { /* ... */ }

    // ...
};

Remarquez que vous n'avez pas besoin d'écrire de lambda ou de deleter personnalisé ici car destroy est déjà un deleter.

146voto

Drew Noakes Points 69288

Il est possible de le faire proprement en utilisant un lambda en C++11 (testé avec G++ 4.8.2).

Étant donné ce typedef réutilisable:

template
using deleted_unique_ptr = std::unique_ptr>;

Vous pouvez écrire:

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

Par exemple, avec un FILE*:

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

Avec cela, vous obtenez les avantages d'un nettoyage sûr des exceptions en utilisant le RAII, sans avoir besoin de traitements d'erreurs try/catch.

102voto

rici Points 45980

Vous devez simplement créer une classe deleter :

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

et la fournir en tant qu'argument de modèle de unique_ptr. Vous devrez toujours initialiser le unique_ptr dans vos constructeurs :

class Foo {
  public:
    Foo() : bar(create()), ... { ... }

  private:
    std::unique_ptr bar;
    ...
};

Autant que je sache, toutes les bibliothèques c++ populaires implémentent cela correctement ; comme BarDeleter n'a en fait aucun état, il n'a pas besoin d'occuper d'espace dans le unique_ptr.

47voto

Quincunx Points 1923

Sauf si vous avez besoin de pouvoir changer le déléteur à l'exécution, je recommande fortement d'utiliser un type de déléteur personnalisé. Par exemple, si vous utilisez un pointeur de fonction pour votre déléteur, sizeof(unique_ptr) == 2 * sizeof(T*). En d'autres termes, la moitié des octets de l'objet unique_ptr sont gaspillés.

Écrire un déléteur personnalisé pour envelopper chaque fonction est ennuyeux, cependant. Heureusement, nous pouvons écrire un type spécialisé sur la fonction :

Depuis C++17 :

template 
struct deleter_from_fn {
    template 
    constexpr void operator()(T* arg) const {
        fn(arg);
    }
};

template 
using my_unique_ptr = std::unique_ptr>;

// utilisation :
my_unique_ptr p{create()};

Avant C++17 :

template 
struct deleter_from_fn {
    template 
    constexpr void operator()(T* arg) const {
        fn(arg);
    }
};

template 
using my_unique_ptr = std::unique_ptr>;

// utilisation :
my_unique_ptr p{create()};

19voto

Deduplicator Points 9096

Vous savez, utiliser un destructeur personnalisé n'est pas la meilleure solution, car vous devrez le mentionner partout dans votre code.
Au lieu de cela, comme vous avez la possibilité d'ajouter des spécialisations aux classes au niveau du namespace dans ::std tant que des types personnalisés sont impliqués et que vous respectez la sémantique, faites ceci :

Spécialisez 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); }
};

Et peut-être faites également 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 };
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X