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