【问题标题】:std::unique_ptr, deleters and the Win32 APIstd::unique_ptr、删除器和 Win32 API
【发布时间】:2013-02-12 20:40:42
【问题描述】:

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

我原以为这会起作用:

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

但在编译时出现错误:

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

当我这样修改构造函数时:

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

我得到了更不寻常的:

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

我现在很茫然。 HANDLE 是 void* 的 typedef:我需要了解一些转换魔法吗?

【问题讨论】:

  • 下面对您的问题有一个很好的答案,但我会认真考虑为 Mutex 滚动我自己的自定义所有者类,而不是为此目的利用 std::unique_ptr 的删除器。 HANDLE 是一个指针这一事实是一个实现细节。它可以很容易地成为一个索引或其他神奇的值。为它制作自己的 RAII 包装器,并让 unique_ptr 管理“真实”指针。
  • @Adrian:我明白你的意思。它还可以在构造函数/析构函数中包含 RAII 等待/释放配对。干杯。

标签: c++ winapi c++11 unique-ptr


【解决方案1】:

暂时忘记自定义删除器。当您说std::unique_ptr&lt;T&gt; 时,unique_ptr 构造函数期望接收T*,但CreateMutex 返回HANDLE,而不是HANDLE *

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

std::unique_ptr<void, deleter> m_mutex;

您必须将CreateMutex 的返回值转换为void *

另一种方法是使用std::remove_pointer 获取HANDLE 的基础类型。

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

另一种方法是利用这样一个事实:如果unique_ptr 的删除器包含一个名为pointer 的嵌套类型,那么unique_ptr 将使用该类型作为其托管对象指针,而不是@987654338 @。

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()) {}

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

因此,指向CloseHandle 的函数指针必须如下所示

BOOL(WINAPI *)(HANDLE)

结合所有这些,

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

我发现改用 lambda 会更容易

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

或者按照cmets中@hjmd的建议,使用decltype来推断函数指针的类型。

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

【讨论】:

  • +1 而不是函数指针,只需使用 decltype(&CloseHandle)
  • 一个 lambda 表达式产生一个值,而不是一个类型。我看不到 lambda 示例将如何编译。
  • 天哪,我只是为了吃一顿晚餐。我喜欢这个网站。感谢您的出色回答。
  • 除了使用decltype推导函数指针的类型外,还可以使用std::ptr_fun(CloseHandle)对其进行初始化,避免使用lambda语法。
  • 读者注意:这段代码只能工作,因为HANDLE是指针类型的别名(可能是void *)。类似的代码不适用于整数句柄,因为 unique_ptr 的指针类型必须满足 NullablePointer
【解决方案2】:

其他人指出了整个HANDLE/HANDLE* 问题的工作原理。这里有一个更聪明的方法来处理它,使用std::unique_pointer 的有趣功能。

struct WndHandleDeleter
{
  typedef HANDLE pointer;

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

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

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

这是可行的,因为HANDLE 是一个指针,因此满足 NullablePointer。

【讨论】:

  • 我有点伤心:我喜欢 Praetorian 答案的最后一行,因为整个解决方案都是本地的,但我喜欢你的解决方案,因为它是一个很好的重用。我会给你+1!谢谢。
  • @MahmoudAl-Qudsi:我不知道您为什么要这样做,但我想您可以声明一个没有名称但带有变量声明的结构。然后使用decltype 来获取它的类型。但是你没有得到任何东西,因为无论哪种方式你都是在命名空间中插入一个名字。
  • +1 这是一个非常不神秘、易于理解的解决方案,也是我最终在日常 make-Windows-behave 头文件中使用的解决方案。非常感谢你,尼科尔。
  • +1。还应该提到这个 unique_ptr 的大小是指针的大小(因为它使用无状态删除器),不像 Praetorian 的最终答案是 2 个指针的大小。
  • 并非所有基于HANDLE 的API 都使用NULL/nullptr 来表示无效句柄。有些人使用INVALID_HANDLE_VALUE (-1) 代替。
【解决方案3】:

问题是你实际上定义了 unque_ptr 来保存指向句柄 (HANDLE*) 类型的指针,但你只传递了 HANDLE,而不是指向它的指针。

【讨论】:

  • Praetorian 的答案非常完整和正确,IHMO。写在我写作的同一时间。
猜你喜欢
  • 1970-01-01
  • 2012-04-11
  • 1970-01-01
  • 2013-03-30
  • 2015-04-09
  • 1970-01-01
  • 2017-05-15
  • 2021-11-10
  • 2014-06-30
相关资源
最近更新 更多