Как использовать пользовательский удаленный элемент с элементом 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 как члена класса?

Ответ 1

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

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

Вы можете написать свой класс Foo следующим образом

class Foo {

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

    // ...

public:

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

    // ...
};

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

Ответ 2

Это можно сделать с помощью лямбда в С++ 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, не требуя шума try/catch.

Ответ 3

Вам просто нужно создать класс 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;
    ...
};

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

Ответ 4

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

Специализировать 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 };
}

Ответ 5

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

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

Ответ 6

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

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

Поскольку С++ 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()};

До С++ 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()};