Reputation: 6318
I am attempting to use std::unique_ptrs to manage Windows HANDLEs in an exception-safe manner.
First I tried:
struct HandleDeleter
{
void operator()( HANDLE handle )
{
if( handle )
{
FindVolumeClose( handle )
}
}
}
typedef std::unique_ptr< HANDLE, HandleDeleter > unique_vol_handle_t;
Later in my code when I try to use it:
unique_vol_handle_t volH( FindFirstVolumeW( buffer, MAX_GUID_PATH ) );
I get the following error from 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
referencing the volH declaration line, immediately above.
After searching for some time, I found a blog article which basically says, add:
typedef HANDLE pointer;
to the top of the struct declaration, and all will be well.
I didn't believe it, but I tried it and it did resolve the error. I'm puzzled how defining a type (without even referencing it) could make such a difference.
Two questions:
Can you explain the original error? I don't understand why the compiler is referring to std::nullptr_t/nullptr
.
How is it that the typedef resolves this (or at least appears to)? Is there a less 'spooky action at a distance' solution to this?
Upvotes: 25
Views: 14304
Reputation: 2372
Please, take a look at Window Implementation Libraries (WIL) in particular at unique_handle
vs unique_hfile
for handling invalid handle values.
Upvotes: 2
Reputation: 185661
The implementation of unique_ptr
checks for the presence of a ::pointer
type on the deleter. If the deleter has a ::pointer
type then this type is used as the pointer
typedef on the unique_ptr
. Otherwise a pointer to the first template argument is used.
According to cppreference.com, the unique_ptr::pointer
type is defined as
std::remove_reference<D>::type::pointer
if that type exists, otherwiseT*
Upvotes: 28
Reputation: 399
While @Levi Haskell his answer takes INVALID_HANDLE_VALUE into account it does not take into account that 0 is a valid handle value and treats INVALID_HANDLE_VALUE and 0 (or nullptr) as the same. This is not correct:
My suggestion (based on Levi Haskell his code so credits to him)
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); }
Upvotes: -1
Reputation: 853
The proper (and safe) way to use std::unique_ptr
for Windows HANDLEs is something like this:
#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;
This way INVALID_HANDLE_VALUE
is implicitly treated as null, and thus will never be passed to CloseHandle
function and you never need to explicitly test for it. Use std::unique_ptr
's operator bool()
instead, as you normally would:
HandlePtr file(CreateFile(...));
if (!file) {
// handle error
}
EDIT: I originally forgot that INVALID_HANDLE_VALUE
is not the only invalid value for the HANDLE
type, and that in fact it is treated as a valid pseudo handle by many, if not most, kernel functions. Well, good to know.
Upvotes: 5
Reputation: 2007
I've been doing the following for various types of handles in Windows. Assuming we have declared somewhere:
std::unique_ptr<void, decltype (&FindVolumeClose)> fv (nullptr, FindVolumeClose);
This is populated with:
HANDLE temp = FindFirstVolume (...);
if (temp != INVALID_HANDLE_VALUE)
fv.reset (temp);
No need to declare a separate struct to wrap the deleters. Since HANDLE
is really a void *
the unique_ptr
takes void
as its type; for other kinds of handles, that use the DECLARE_HANDLE
macro, this can be avoided:
// Manages the life of a HHOOK
std::unique_ptr<HHOOK__, decltype (&UnhookWindowsHookEx)> hook (nullptr, UnhookWindowsHookEx);
And so on.
Upvotes: 7
Reputation: 409166
From the MSDN manual on unique_ptr:
The stored pointer to an owned resource,
stored_ptr
has type pointer. It isDel::pointer
if defined, andType *
if not. The stored deleter objectstored_deleter
occupies no space in the object if the deleter is stateless. Note that Del can be a reference type.
This means that if you provide a deleter functor it have to provide a pointer
type that is used for the actual pointer type of the unique_ptr
. Otherwise it will be the a pointer to your provided type, in your case HANDLE*
which isn't correct.
Upvotes: 6