使用带有自定义删除器的 shared_ptr 使 HANDLE 符合 RAII 规范

12
我最近在Stack Overflow上发布了一个关于RAII的一般性问题:SO。然而,我在处理HANDLE示例时仍然存在一些实现问题。
在windows.h中,HANDLE被typedef为void *。因此,正确的shared_ptr定义应为:
std::tr1::shared_ptr<void> myHandle (INVALID_HANDLE_VALUE, CloseHandle);

示例 1 CreateToolhelp32Snapshot: 返回HANDLE并起作用。

const std::tr1::shared_ptr<void> h
    (CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL), CloseHandle);

在定义中使用void(哪种方式正确?)会导致问题,当我尝试使用此指针调用一些更多的winapi命令时。它们功能上是有效的,但很丑陋,我相信一定有更好的解决方案。

在以下示例中,h是通过顶部的定义创建的指针。

示例2 OpenProcessToken: 最后一个参数是PHANDLE。强制转换使其有点丑陋。

OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
    (PHANDLE)&h);

示例 3 Process32First: 第一个参数是一个HANDLE。真的很丑。

Process32First(*((PHANDLE)&h), &pEntry);

示例 4 使用常量 HANDLE 进行简单比较。非常丑陋。

if (*((PHANDLE)&h) == INVALID_HANDLE) { /* do something */ }

如何正确创建一个适用于 HANDLE 的 shared_ptr?


1
不过,可以通过使用type_traits中的std::remove_pointer <HANDLE> ::type来稍微减少使用void类型的“丑陋”。 - Andrey Starodubtsev
4个回答

11

例子1是正确的。

例子2是错误的。通过盲目地将指针转换为PHANDLE,会绕过shared_ptr的逻辑。应该改成这样:

HANDLE h;
OpenProcessToken(...., &h);
shared_ptr<void> safe_h(h, &::CloseHandle);

或者,将值分配给一个已存在的 shared_ptr:

shared_ptr<void> safe_h = ....
{
  HANDLE h;
  OpenProcessToken(...., &h);
  safe_h.reset(h, &::CloseHandle);
}//For extra safety, limit visibility of the naked handle

或者,创建一个安全的 OpenProcessToken 版本,该版本返回共享句柄而不是使用 PHANDLE:

// Using SharedHandle defined at the end of this post
SharedHandle OpenProcess(....)
{
    HANDLE h = INVALID_HANDLE_VALUE;
    ::OpenProcessToken(...., &h);
    return SharedHandle(h);
}

例子3:不需要走这些弯路。这个应该可以:

Process32First(h.get(), ...);

示例4:再次,没有绕路:

if (h.get() == INVALID_HANDLE){...}

为了让事情更好,你可以定义一个类似于以下的typedef:

typedef shared_ptr<void> SharedHandle;

或者更好的方法是,如果所有句柄都需要用CloseHandle()关闭,那么创建一个SharedHandle类来封装一个shared_ptr并自动提供正确的删除器:

// Warning: Not tested. For illustration purposes only
class SharedHandle
{
public:
  explicit SharedHandle(HANDLE h) : m_Handle(h, &::CloseHandle){};
  HANDLE get()const{return m_Handle.get();}

  //Expose other shared_ptr-like methods as needed
  //...

private:
  shared_ptr<void> m_Handle;
};

在您的第一个示例2代码片段中,是否有可能在将其转换为安全句柄后删除不安全的“HANDLE”? - Etan
你可以创建一个包装OpenProcessHandle()的函数(我已将其添加到帖子中),或者与第二个片段中的操作相同,其中shared_h初始化为INVALID_HANDLE_VALUE。 - Éric Malenfant

3

不要使用 shared_ptr,可以使用 ATL::CHandle。

原因如下:

  • 当您看到 CHandle 时,您知道它是一个句柄的 RAII 包装器。
  • 当您看到 shared_ptr<void> 时,您不知道它是什么。
  • CHandle 不会将所有权共享(但在某些情况下,您可能需要共享所有权)。
  • CHandle 是 Windows 开发栈的标准。
  • CHandle 比具有自定义删除器的 shared_ptr<void> 更紧凑(更少的输入/阅读)。

你能详细说明一下吗?文档并没有提供太多信息,但它看起来更像是一个unique_ptr,我不明白CHandle如何促进共享。 - phant0m
@phant0m CHandle不提供共享所有权。 - Sergey Podobry

2

这是我的另一种方案,它非常好,除了需要在每次使用 .get() 后解引用并且需要使用函数对象或lambda表达式:

template<typename HandleType, typename Deleter>
std::shared_ptr<HandleType> make_shared_handle(HandleType _handle, Deleter _dx)
{
    return std::shared_ptr<HandleType>(new HandleType(_handle), _dx);
}

那么:

auto closeHandleDeleter = [](HANDLE* h) {
    ::CloseHandle(*h);
    delete h;
};
std::shared_ptr<HANDLE> sp = make_shared_handle(a_HANDLE, closeHandleDeleter);
f_that_takes_handle(*sp.get());

我最喜欢的是,使用这个工具不需要额外的工作量即可访问它:
std::weak_ptr<HANDLE> wp = sp; // Yes. This could make sense in some designs.

当然,这个辅助函数适用于任何类似的句柄类型。


2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接