#cpp
У std::unique_ptr можно задать свой deleter - шаблонный параметр, который отвечает
за удаление объекта. Это позволяет использовать std::unique_ptr не только для памяти
выделенной с помощью new, но и для разного рода "хендлов" на объекты. Как лучше это
сделать?
Ответы
Ответ 1
Допустим у нас следующее API: typedef unspecified_type* HANDLE; // указатель на какой-то тип HANDLE CreateObject(); bool CloseHandle(HANDLE handle); Вспомогательный класс Мы можем написать свой класс-deleter: struct HandleDeleter { void operator()(HANDLE handle) { ::CloseHandle(handle); } }; И с его помощью сделать typedef для std::unique_ptr: #include#include using UniqueHandle = std::unique_ptr< std::remove_pointer ::type, HandleDeleter>; UniqueHandle handle(CreateObject(123)); Лямбда-выражение Если мы не хотим писать свой класс-"функтор", то его можно создать с помощью лямбда-выражения: const auto handle_deleter = [](HANDLE handle){ CloseHandle(handle); }; using UniqueHandle = std::unique_ptr< std::remove_pointer ::type, decltype(handle_deleter)>; UniqueHandle handle(CreateObject(123), handle_deleter); (Переменная handle_deleter определена как const, а значит она может быть в заголовочном файле.) Проблема в том, что тип лямбда-выражения (объект-замыкание) не имеет конструктора по умолчанию, а значит мы должны передавать его в конструктор std::unique_ptr. Указатель на функцию (простое решение) Лямбда выражение выше просто вызывает CloseHandle, и мы вполне можем заменить его на указатель на функцию: using UniqueHandle = std::unique_ptr< std::remove_pointer ::type, decltype(&CloseHandle)>; UniqueHandle handle(CreateObject(123), CloseHandle); Проблема с конструктором остается - значение по умолчанию указателя на функцию - это nullptr, и нам надо инициализировать его указателем на саму CloseHandle. Указатель на функцию (хорошее решение) Итак, нам надо взять указатель на функцию CloseHandle, и превратить его значение в тип. Для этого в стандартной библиотеке есть класс std::integral_constant, который принимает параметр шаблона не-тип, и имеет соответствующий оператор преобразования: template struct integral_constant { ... constexpr operator T() { return v; } }; Если мы используем его в качестве deleter, то он будет преобразовываться к указателю на CloseHandle: using UniqueHandle = std::unique_ptr< std::remove_pointer ::type, std::integral_constant >; UniqueHandle handle(CreateObject(123)); Хендл не-указатель Что делать если тип хендла - это не указатель? Например typedef unsigned long HANDLE; Тогда надо писать свой класс-deleter. std::unique_ptr не использует T* напрямую, а вместо этого он должен использовать Deleter::pointer если такой тип есть. При этом тип pointer должен уметь сравниваться с nullptr, по этому мы должны сделать его классом: struct HandleDeleter { struct pointer { pointer() {} pointer(std::nullptr_t) {} pointer(HANDLE handle) : value_(handle) {} operator HANDLE() { return value_; } friend bool operator==(pointer lhs, pointer rhs) { return lhs.value_ == rhs.value_; } friend bool operator!=(pointer lhs, pointer rhs) { return !(lhs == rhs); } HANDLE value_ = 0; }; void operator()(pointer p) { ::CloseHandle(p.value_); } }; using UniqueHandle = std::unique_ptr ; UniqueHandle handle(CreateObject(123)); operator HANDLE() нужен для того, чтобы мы могли писать handle.get() вместо handle.get().value_. Он же работает и как оператор приведения к bool. Теоретически, если невалидное значение хендла отличается от нуля, мы можем написать const HANDLE INVALID_HANDLE = -1; ... HANDLE value_ = INVALID_HANDLE; И согласно стандарту это будет работать, т.к. реализация std::unique_ptr должна везде использовать if (p_ == nullptr) вместо if (p), в том числе и в реализации unique_ptr::operator bool(). Однако поведение кода может быть весьма не очевидно: UniqueHandle empty; assert(empty == nullptr); // OK, равно nullptr assert(!empty); // OK, преобразуется в false assert(empty.get() == nullptr); // OK, равно nullptr assert(empty.get()); // O_o не равно нулю
Комментариев нет:
Отправить комментарий