U007D
U007D

Reputation: 6318

Using std::unique_ptr for Windows HANDLEs

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?

Upvotes: 25

Views: 14304

Answers (6)

ivan_onys
ivan_onys

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

Lily Ballard
Lily Ballard

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

Upvotes: 28

Air2
Air2

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

Levi Haskell
Levi Haskell

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

Sam Morris
Sam Morris

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

Some programmer dude
Some programmer dude

Reputation: 409166

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.

Upvotes: 6

Related Questions