std :: unique_ptr,deleters和Win32 API

hat*_*cat 26 c++ winapi unique-ptr c++11

在VC2012中,我想使用唯一指针和删除器在构造函数中创建一个互斥锁,这样我就不需要创建一个析构函数来调用CloseHandle.

我原以为这会起作用:

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

但在编译时我收到一个错误:

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

当我修改构造函数时:

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

我变得更加不寻常:

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

我现在不知所措.HANDLE是void*的typedef:我需要了解一些转换魔法吗?

Pra*_*ian 47

暂时忘掉自定义删除器.当你说std::unique_ptr<T>,unique_ptr构造函数期望接收a T*,但CreateMutex返回a HANDLE,而不是a HANDLE *.

有3种方法可以解决这个问题:

std::unique_ptr<void, deleter> m_mutex;
Run Code Online (Sandbox Code Playgroud)

你必须将返回值转换CreateMutex为a void *.

另一种方法是使用std::remove_pointer到达HANDLE底层类型.

std::unique_ptr<std::remove_pointer<HANDLE>::type, deleter> m_mutex;
Run Code Online (Sandbox Code Playgroud)

另一种方法是利用如下事实:如果unique_ptr删除器包含一个名为的嵌套类型pointer,那么unique_ptr将使用该类型作为其托管对象指针而不是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()) {}
Run Code Online (Sandbox Code Playgroud)

现在,如果要将指向函数类型的指针作为删除器传递,那么在处理Windows API时,还需要在创建函数指针时注意调用约定.

所以,一个函数指针CloseHandle必须看起来像这样

BOOL(WINAPI *)(HANDLE)
Run Code Online (Sandbox Code Playgroud)

结合所有这些,

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

我发现使用lambda更容易

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

或者如@hjmd在评论中所建议的那样,decltype用来推断函数指针的类型.

std::unique_ptr<std::remove_pointer<HANDLE>::type, 
                decltype(&::CloseHandle)> m_mutex(::CreateMutex(NULL, FALSE, NULL),
                                                  &::CloseHandle);
Run Code Online (Sandbox Code Playgroud)

  • +1而不是函数指针只使用decltype(&CloseHandle) (6认同)

Nic*_*las 31

其他人指出整个HANDLE/ HANDLE*问题是如何运作的.使用有趣的功能,这是一个更聪明的方式来处理它std::unique_pointer.

struct WndHandleDeleter
{
  typedef HANDLE pointer;

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

typedef std::unique_ptr<HANDLE, WndHandleDeleter> unique_handle;
Run Code Online (Sandbox Code Playgroud)

这允许unique_handle::get返回HANDLE而不是HANDLE*没有任何花哨std::remove_pointer或其他这样的东西.

这是因为HANDLE是一个指针,因此满足NullablePointer.

  • 并非所有基于`HANDLE`的API都使用`NULL` /`nullptr`来表示无效句柄.有些人使用`INVALID_HANDLE_VALUE`(-1)代替. (5认同)
  • +1.还应该提到这个unique_ptr的大小是一个指针的大小(因为它使用无状态删除器),这与Praetorian的最终答案不同,后者是2个指针的大小. (3认同)