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:
1) Can you explain the original error? I don't understand why the compiler is referring to std::nullptr_t/nullptr
.
2) 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?
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)
From the MSDN manual on unique_ptr:
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 theunique_ptr
. Otherwise it will be the a pointer to your provided type, in your caseHANDLE*
which isn't correct.I've been doing the following for various types of handles in Windows. Assuming we have declared somewhere:
This is populated with:
No need to declare a separate struct to wrap the deleters. Since
HANDLE
is really avoid *
theunique_ptr
takesvoid
as its type; for other kinds of handles, that use theDECLARE_HANDLE
macro, this can be avoided:And so on.
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 thepointer
typedef on theunique_ptr
. Otherwise a pointer to the first template argument is used.According to cppreference.com, the
unique_ptr::pointer
type is defined asI recommend you to take a look at A Proposal to Add additional RAII Wrappers to the Standard Library and at the drawbacks of using smart pointers with handles.
Personally, I'm making use of the reference implementation of that proposal instead of using
std::unique_ptr
for these situations.The proper (and safe) way to use
std::unique_ptr
for Windows HANDLEs is something like this:This way
INVALID_HANDLE_VALUE
is implicitly treated as null, and thus will never be passed toCloseHandle
function and you never need to explicitly test for it. Usestd::unique_ptr
'soperator bool()
instead, as you normally would:EDIT: I originally forgot that
INVALID_HANDLE_VALUE
is not the only invalid value for theHANDLE
type, and that in fact it is treated as a valid pseudo handle by many, if not most, kernel functions. Well, good to know.