Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 296
U007D Avatar asked Aug 29 '12 18:08

U007D


People also ask

When should we use unique_ptr?

Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.

In what situation is a shared_ptr more appropriate than a unique_ptr?

Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.

Can you pass unique_ptr by value?

A unique_ptr does not share its pointer. It cannot be copied to another unique_ptr , passed by value to a function, or used in any C++ Standard Library algorithm that requires copies to be made. A unique_ptr can only be moved.

Can I assign unique_ptr?

It can be assigned: class owner { std::unique_ptr<someObject> owned; public: owner() { owned=std::unique_ptr<someObject>(new someObject()); } };


3 Answers

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*

like image 53
Lily Ballard Avatar answered Sep 24 '22 16:09

Lily Ballard


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.

like image 34
Some programmer dude Avatar answered Sep 23 '22 16:09

Some programmer dude


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.

like image 23
Sam Morris Avatar answered Sep 20 '22 16:09

Sam Morris