Using std::unique_ptr for Windows HANDLEs

2019-01-24 02:45发布

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?

6条回答
疯言疯语
2楼-- · 2019-01-24 03:09

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); }
查看更多
一纸荒年 Trace。
3楼-- · 2019-01-24 03:11

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.

查看更多
小情绪 Triste *
4楼-- · 2019-01-24 03:21

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.

查看更多
Anthone
5楼-- · 2019-01-24 03:26

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*

查看更多
爱情/是我丢掉的垃圾
6楼-- · 2019-01-24 03:28

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.

查看更多
beautiful°
7楼-- · 2019-01-24 03:29

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.

查看更多
登录 后发表回答