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