25

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?

dandan78
  • 13,328
  • 13
  • 64
  • 78
U007D
  • 5,772
  • 2
  • 38
  • 44

7 Answers7

27

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, otherwise T*

Werner Henze
  • 16,404
  • 12
  • 44
  • 69
Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • 1
    Thank you, Kevin for the clear explanation. And by not defining the pointer type, I'm basically telling the deleter to use HANDLE* (void**) instead of HANDLE (void*), which is incorrect. The compiler gave me the world's most cryptic error message, but your explanation is in plain English, so I'm accepting your answer--thank you! – U007D Aug 29 '12 at 19:37
  • Is that useful, though? The standard says that `operator*` returns `*get()`, but has return type `T &`, and `get()` has return type `pointer`. – Kerrek SB Feb 08 '14 at 01:57
  • 1
    For most handles 0 is a valid value and INVALID_HANDLE_VALUE is the invalid value, therefor this solution is not correct for most handle because the reset() of the unique_ptr will check for nullptr instead of INVALID_HANDLE_VALUE so you have to create your own wrapper class. – Air2 Dec 19 '17 at 09:29
  • 1
    So the answer (for the lazy) is to add a `typedef` like the following: `struct HandleDeleter { typedef HANDLE pointer; void operator()( HANDLE handle )` ... – Liviu Jan 11 '18 at 16:37
7

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.

Sam Morris
  • 1,858
  • 1
  • 17
  • 18
  • 3
    `HANDLE` is not `void*` if `STRICT` is defined. You can use `std::remove_pointer` instead of hard-coding the type, ie `std::remove_pointer::type`, `std::remove_pointer::type`, etc, that way you always have the correct type whether `STRICT` is defined or not – Remy Lebeau May 10 '19 at 22:40
  • 1
    @RemyLebeau: `STRICT` turns GDI and user handles (like `HBRUSH`, `HDC`, `HMONITOR`, `HINSTANCE`, `HKEY`, etc.) into distinct types. But kernel `HANDLE`s are all `void *`, even the ones that require something other than `CloseHandle` to close them, like the ones returned from `FindFirstVolume`, `FindFirstFile`, etc. – Adrian McCarthy Aug 28 '20 at 13:34
6

From the MSDN manual on unique_ptr:

The stored pointer to an owned resource, stored_ptr has type pointer. It is Del::pointer if defined, and Type * if not. The stored deleter object stored_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.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Hi, Joachim. Kevin's explanation above is clearer to me than MSDN's language so I've accepted his answer. But this does make sense now. Thank you! – U007D Aug 29 '12 at 19:40
5

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.

Levi Haskell
  • 795
  • 8
  • 20
  • Can you generalize this a little to allow different specializations? For example, WinHandle, vs WinHandle(CloseHandle), as well as for unique_ptr vs. shared_ptr? Also, I wrapped the WinHandle in a namespace to deter it being used directly. – joeking Apr 16 '21 at 18:42
3

I 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.

Community
  • 1
  • 1
oblitum
  • 11,380
  • 6
  • 54
  • 120
  • 1
    I can't say I fully agree with the 'drawbacks of using smart pointers with handles' premise, but I do appreciate the links to interesting and thought-provoking articles such as this. Thank you. – U007D Aug 23 '16 at 16:35
2

Please, take a look at Window Implementation Libraries (WIL) in particular at unique_handle vs unique_hfile for handling invalid handle values.

ivan_onys
  • 2,282
  • 17
  • 21
-1

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); }
Air2
  • 369
  • 3
  • 13
  • Note that when `STRICT` is defined, Win32 handle types are not simply `void*` anymore, they are typed structs instead, so you can't just use `HANDLE` generically under `STRICT`. I would suggest adding an additional template parameter to take the actual handle type: `template class cWinHandle { ... };` – Remy Lebeau May 15 '19 at 19:32