Использование std:: unique_ptr для Windows HANDLE
Я пытаюсь использовать std :: unique_ptrs для управления Windows HANDLE безопасным способом.
Сначала я попробовал:
struct HandleDeleter
{
void operator()( HANDLE handle )
{
if( handle )
{
FindVolumeClose( handle )
}
}
}
typedef std::unique_ptr< HANDLE, HandleDeleter > unique_vol_handle_t;
Позже в моем коде, когда я пытаюсь использовать его:
unique_vol_handle_t volH( FindFirstVolumeW( buffer, MAX_GUID_PATH ) );
Я получаю следующую ошибку от Visual Studio 2012RC:
1> error C2664: 'std::unique_ptr<_Ty,_Dx>::unique_ptr(std::nullptr_t) throw()' : cannot convert parameter 1 from 'HANDLE' to 'std::nullptr_t'
1> with
1> [
1> _Ty=HANDLE,
1> _Dx=VolumeHandleDeleter
1> ]
1> nullptr can only be converted to pointer or handle types
ссылаясь на строку объявления volH, сразу выше.
После поиска в течение некоторого времени я нашел статью в блоге, в которой, в основном, сказано:
typedef HANDLE pointer;
к началу объявления структуры, и все будет хорошо.
Я не поверил, но я попробовал, и это действительно помогло исправить ошибку. Я озадачен тем, как определение типа (даже без ссылки на него) может иметь такое значение.
Два вопроса:
1) Можете ли вы объяснить оригинальную ошибку? Я не понимаю, почему компилятор ссылается на std::nullptr_t/nullptr
.
2) Как typedef разрешает это (или, по крайней мере, кажется)? Есть ли менее пугающее решение на расстоянии?
Ответы
Ответ 1
Реализация unique_ptr
проверяет наличие типа ::pointer
на удалитель. Если средство удаления имеет тип ::pointer
тогда этот тип используется в качестве pointer
typedef для unique_ptr
. В противном случае используется указатель на первый аргумент шаблона.
Согласно cppreference.com, тип unique_ptr unique_ptr::pointer
определяется как
std::remove_reference<D>::type::pointer
если этот тип существует, в противном случае T*
Ответ 2
Из руководства MSDN по unique_ptr:
Сохраненный указатель на принадлежащий ему ресурс stored_ptr
имеет указатель на тип. Это Del::pointer
, если определено, и Type *
, если нет. Сохраненный объект deleter stored_deleter
не занимает места в объекте, если deleter не имеет состояния. Обратите внимание, что Del может быть ссылочным типом.
Это означает, что если вы предоставляете функтор deleter, он должен предоставить тип pointer
, который используется для фактического типа указателя unique_ptr
. В противном случае это будет указатель на ваш предоставленный тип, в вашем случае HANDLE*
, что неверно.
Ответ 3
Я рекомендую вам взглянуть на Предложение добавить дополнительные обтекатели RAII в стандартную библиотеку и недостатки использования интеллектуальных указателей с ручками.
Лично я использую эталонную реализацию этого предложения вместо использования std::unique_ptr
для этих ситуаций.
Ответ 4
Я делал следующее для различных типов ручек в Windows. Предполагая, что мы где-то заявили:
std::unique_ptr<void, decltype (&FindVolumeClose)> fv (nullptr, FindVolumeClose);
Это заполняется:
HANDLE temp = FindFirstVolume (...);
if (temp != INVALID_HANDLE_VALUE)
fv.reset (temp);
Не нужно объявлять отдельную структуру для обертки удалений. Так как HANDLE
действительно a void *
, то unique_ptr
принимает void
в качестве своего типа; для других видов ручек, которые используют макрос DECLARE_HANDLE
, этого можно избежать:
// Manages the life of a HHOOK
std::unique_ptr<HHOOK__, decltype (&UnhookWindowsHookEx)> hook (nullptr, UnhookWindowsHookEx);
И так далее.
Ответ 5
Правильный (и безопасный) способ использования std::unique_ptr
для Windows HANDLEs выглядит примерно так:
#include <windows.h>
#include <memory>
class WinHandle {
HANDLE value_;
public:
WinHandle(std::nullptr_t = nullptr) : value_(nullptr) {}
WinHandle(HANDLE value) : value_(value == INVALID_HANDLE_VALUE ? nullptr : value) {}
explicit operator bool() const { return value_ != nullptr; }
operator HANDLE() const { return value_; }
friend bool operator ==(WinHandle l, WinHandle r) { return l.value_ == r.value_; }
friend bool operator !=(WinHandle l, WinHandle r) { return !(l == r); }
struct Deleter {
typedef WinHandle pointer;
void operator()(WinHandle handle) const { CloseHandle(handle); }
};
};
inline bool operator ==(HANDLE l, WinHandle r) { return WinHandle(l) == r; }
inline bool operator !=(HANDLE l, WinHandle r) { return !(l == r); }
inline bool operator ==(WinHandle l, HANDLE r) { return l == WinHandle(r); }
inline bool operator !=(WinHandle l, HANDLE r) { return !(l == r); }
typedef std::unique_ptr<WinHandle, WinHandle::Deleter> HandlePtr;
Этот способ INVALID_HANDLE_VALUE
неявно рассматривается как null и поэтому никогда не будет передан функции CloseHandle
, и вам не нужно явно проверять его. Вместо этого используйте std::unique_ptr
operator bool()
, как обычно:
HandlePtr file(CreateFile(...));
if (!file) {
// handle error
}
EDIT: Я изначально забыл, что INVALID_HANDLE_VALUE
не единственное недопустимое значение для типа HANDLE
и что на самом деле он рассматривается как действительный псевдо-дескриптор многими, если не большинством, функциями ядра. Хорошо, хорошо знать.
Ответ 6
В то время как @Levi Haskell его ответ принимает INVALID_HANDLE_VALUE, он не принимает во внимание, что 0 является допустимым значением дескриптора и рассматривает INVALID_HANDLE_VALUE и 0 (или nullptr) как то же самое. Это неверно:
Мое предложение (основано на Леви Хаскелле, его код так за него кредитует)
template<void *taInvalidHandleValue>
class cWinHandle
{
HANDLE value_ { taInvalidHandleValue };
public:
cWinHandle() = default;
cWinHandle(HANDLE value) : value_(value) {}
~cWinHandle()
{
reset();
}
explicit operator bool() const { return value_ != taInvalidHandleValue; }
operator HANDLE() const { return value_; }
HANDLE get() const { return value_; }
HANDLE release() { HANDLE const result{ value_ }; value_ = taInvalidHandleValue; return result; }
friend bool operator ==(cWinHandle l, cWinHandle r) { return l.value_ == r.value_; }
friend bool operator !=(cWinHandle l, cWinHandle r) { return !(l == r); }
void reset ()
{
if (value_ != taInvalidHandleValue)
{
CloseHandle(value_);
value_ = taInvalidHandleValue;
}
}
};
inline bool operator ==(HANDLE l, cWinHandle<nullptr> r) { return cWinHandle<nullptr>(l) == r; }
inline bool operator !=(HANDLE l, cWinHandle<nullptr> r) { return !(l == r); }
inline bool operator ==(cWinHandle<nullptr> l, HANDLE r) { return l == cWinHandle<nullptr>(r); }
inline bool operator !=(cWinHandle<nullptr> l, HANDLE r) { return !(l == r); }
inline bool operator ==(HANDLE l, cWinHandle<INVALID_HANDLE_VALUE> r) { return cWinHandle<INVALID_HANDLE_VALUE>(l) == r; }
inline bool operator !=(HANDLE l, cWinHandle<INVALID_HANDLE_VALUE> r) { return !(l == r); }
inline bool operator ==(cWinHandle<INVALID_HANDLE_VALUE> l, HANDLE r) { return l == cWinHandle<INVALID_HANDLE_VALUE>(r); }
inline bool operator !=(cWinHandle<INVALID_HANDLE_VALUE> l, HANDLE r) { return !(l == r); }