Как использовать пользовательский удалитель с элементом std::unique ptr?


у меня есть класс с членом unique_ptr не.

class Foo {
private:
    std::unique_ptr<Bar> bar;
    ...
};

бар является сторонним классом, который имеет функцию create() и функцию destroy ().

если бы я хотел использовать std::unique_ptr С ним в Автономной функции я мог бы сделать:

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

есть ли способ сделать это с помощью std::unique_ptr как член класса?

6 90

6 ответов:

предполагая, что create и destroy являются свободными функциями (что, по-видимому, относится к фрагменту кода OP) со следующими сигнатурами:

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

вы можете написать свой класс Foo такой

class Foo {

    std::unique_ptr<Bar, void(*)(Bar*)> ptr_;

    // ...

public:

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

    // ...
};

обратите внимание, что вам не нужно писать здесь лямбда или пользовательский делетер, потому что destroy - это уже делетер.

Это можно сделать чисто с помощью лямбда в C++11 (проверено в G++ 4.8.2).

учитывая это многоразовые typedef:

template<typename T>
using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;

вы можете написать:

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

например,FILE*:

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

С этим вы получаете преимущества исключения безопасной очистки с помощью RAII, без необходимости попробовать / поймать шум.

вам просто нужно создать класс deleter:

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

и предоставить его в качестве аргумента шаблона unique_ptr. Вам все равно придется инициализировать unique_ptr в ваших конструкторах:

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

  private:
    std::unique_ptr<Bar, BarDeleter> bar;
    ...
};

насколько я знаю, все популярные библиотеки c++ реализуют это правильно; так как BarDeleter на самом деле не имеет никакого состояния, он не должен занимать никакого места в unique_ptr.

вы знаете, что использование пользовательского deleter-это не лучший способ, так как вам придется упомянуть об этом во всем вашем коде.
Вместо этого,как вы можете добавить специализации к классам уровня пространства имен в ::std пока задействованы пользовательские типы и вы уважаете семантику, сделайте это:

Specialize 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); }
};

а может и сделать 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 };
}

вы можете просто использовать std::bind С вашей функцией уничтожить.

std::unique_ptr<Bar, std::function<void(Bar*)>> bar(create(), std::bind(&destroy,
    std::placeholders::_1));

но, конечно, вы также можете использовать лямбда.

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

Если вам не нужно иметь возможность изменить делетер во время выполнения, я настоятельно рекомендую использовать пользовательский тип делетера. Например, если использовать указатель на функцию для удаления, sizeof(unique_ptr<T, fptr>) == 2 * sizeof(T*). Другими словами, половина байтов unique_ptr "объект" теряется.

написание пользовательского делетера для обертывания каждой функции-это проблема. К счастью, мы можем написать тип, шаблонный для функции:

Начиная С 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()};

до 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()};