Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::unique_ptr, deleters and the Win32 API

In VC2012, I want to create a mutex in a constructor using a unique pointer and a deleter, so that I don't need to create a destructor just to call CloseHandle.

I would have thought that this would work:

struct foo
{
    std::unique_ptr<HANDLE, BOOL(*)(HANDLE)> m_mutex;
    foo() : m_mutex(CreateMutex(NULL, FALSE, NULL), CloseHandle) {}
}

but on compiling I get an error:

error C2664: 'std::unique_ptr<_Ty,_Dx>::unique_ptr(void *,int 
(__cdecl *const &)(HANDLE)) throw()' : cannot convert parameter 1 from 
'HANDLE' to 'void *'

When I modify the constructor thus:

foo() : m_mutex((void*)CreateMutex(NULL, FALSE, 
    (name + " buffer mutex").c_str()), CloseHandle) {}

I get the even more unusual:

error C2664: 'std::unique_ptr<_Ty,_Dx>::unique_ptr(void *,
int (__cdecl *const &)(HANDLE)) throw()' : cannot convert 
parameter 1 from 'void *' to 'void *'

I'm at a loss now. HANDLE is a typedef for void*: is there some conversion magic I need to know about?

like image 511
hatcat Avatar asked Feb 12 '13 20:02

hatcat


People also ask

What is std :: unique_ptr?

std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.

When should we use unique_ptr?

When to 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.

What does unique_ptr get do?

std::unique_ptr::get Returns the stored pointer. The stored pointer points to the object managed by the unique_ptr, if any, or to nullptr if the unique_ptr is empty.

Does unique_ptr call Delete?

unique_ptr objects automatically delete the object they manage (using a deleter) as soon as they themselves are destroyed, or as soon as their value changes either by an assignment operation or by an explicit call to unique_ptr::reset.


3 Answers

Forget about the custom deleter for now. When you say std::unique_ptr<T>, the unique_ptr constructor expects to receive a T*, but CreateMutex returns a HANDLE, not a HANDLE *.

There are 3 ways to fix this:

std::unique_ptr<void, deleter> m_mutex;

You'll have to cast the return value of CreateMutex to a void *.

Another way to do this is use std::remove_pointer to get to the HANDLE's underlying type.

std::unique_ptr<std::remove_pointer<HANDLE>::type, deleter> m_mutex;

Yet another way to do this is to exploit the fact that if the unique_ptr's deleter contains a nested type named pointer, then the unique_ptr will use that type for its managed object pointer instead of T*.

struct mutex_deleter {
  void operator()( HANDLE h ) 
  {
    ::CloseHandle( h );
  }
  typedef HANDLE pointer;
};
std::unique_ptr<HANDLE, mutex_deleter> m_mutex;
foo() : m_mutex(::CreateMutex(NULL, FALSE, NULL), mutex_deleter()) {}

Now, if you want to pass a pointer to function type as the deleter, then when dealing with the Windows API you also need to pay attention to the calling convention when creating function pointers.

So, a function pointer to CloseHandle must look like this

BOOL(WINAPI *)(HANDLE)

Combining all of it,

std::unique_ptr<std::remove_pointer<HANDLE>::type, 
                BOOL(WINAPI *)(HANDLE)> m_mutex(::CreateMutex(NULL, FALSE, NULL),
                                                &::CloseHandle);

I find it easier to use a lambda instead

std::unique_ptr<std::remove_pointer<HANDLE>::type, 
                void(*)( HANDLE )> m_mutex;
foo() : m_mutex(::CreateMutex(NULL, FALSE, NULL), 
                []( HANDLE h ) { ::CloseHandle( h ); }) {}

Or as suggested by @hjmd in the comments, use decltype to deduce the type of the function pointer.

std::unique_ptr<std::remove_pointer<HANDLE>::type, 
                decltype(&::CloseHandle)> m_mutex(::CreateMutex(NULL, FALSE, NULL),
                                                  &::CloseHandle);
like image 64
Praetorian Avatar answered Sep 22 '22 19:09

Praetorian


Others have pointed out how the whole HANDLE/HANDLE* issue works. Here's a much cleverer way to deal with it, using interesting features of std::unique_pointer.

struct WndHandleDeleter
{
  typedef HANDLE pointer;

  void operator()(HANDLE h) {::CloseHandle(h);}
};

typedef std::unique_ptr<HANDLE, WndHandleDeleter> unique_handle;

This allows unique_handle::get to return HANDLE instead of HANDLE*, without any fancy std::remove_pointer or other such things.

This works because HANDLE is a pointer and therefore satisfies NullablePointer.

like image 44
Nicol Bolas Avatar answered Sep 22 '22 19:09

Nicol Bolas


The problem is you actually define unque_ptr that holds pointer to handle (HANDLE*) type, but you pass just HANDLE, not pointer to it.

like image 23
zen-cat Avatar answered Sep 22 '22 19:09

zen-cat