3

Windows 句柄有时很烦人,记得清理之后(使用创建的笔和画笔进行 GDI 就是一个很好的例子)。RAII 解决方案很棒,但是为每种不同类型的手柄制作一个完整的(五规则)RAII 类真的很棒吗?当然不是!我能看到的最好的将是一个完整的通用 RAII 类,其他类只定义应该清理句柄时要做什么,以及其他特定于句柄的方面。

例如,一个非常简单的模块类可以这样定义(只是一个例子):

struct Module {
    Module() : handle_{nullptr} {}
    Module(HMODULE hm) : handle_{hm, [](HMODULE h){FreeLibrary(h);}} {}
    operator HMODULE() const {return handle_.get();}

private:
    Handle<HMODULE> handle_;
};

这一切都很好,花花公子,不需要析构函数或任何东西。当然,虽然能够将Handle类编写为不需要析构函数或其他任何东西也很好。为什么不使用现有的 RAII 技术?一个想法是使用指向 a 的智能指针void,但这不起作用。以下是在正常情况下实际声明句柄的方式:

#define DECLARE_HANDLE(n) typedef struct n##__{int i;}*n
DECLARE_HANDLE(HACCEL);
DECLARE_HANDLE(HBITMAP);
DECLARE_HANDLE(HBRUSH);
...

它实际上区分了句柄类型,这很好,但它使使用智能指针void变得不可能。相反,如果根据定义,句柄是指针,那么可以提取类型怎么办?

我的问题是以下是否是一个安全的假设。它使用桌面的句柄,该句柄必须关闭。除非共享指针和唯一指针之间存在差异(例如,FreeLibrary具有自己的引用计数语义),否则假设句柄是一个指针,并将智能指针指向它所指向的任何内容,或者我应该不使用智能指针并Handle实现 RAII方面本身?

#include <memory>
#include <type_traits>
#include <utility>
#include <windows.h>

int main() {
    using underlying_type = std::common_type<decltype(*std::declval<HDESK>())>::type;
    std::shared_ptr<underlying_type> ptr{nullptr, [](HDESK desk){CloseDesktop(desk);}};
}
4

4 回答 4

2

您可以采取的一种方法是使用模板类:

template<typename H, BOOL(WINAPI *Releaser)(H)>
class Handle
{
private:
    H m_handle;

public:
    Handle(H handle) : m_handle(handle) { }
    ~Handle() { (*Releaser)(m_handle); }
};

typedef Handle<HANDLE,&::CloseHandle> RAIIHANDLE;
typedef Handle<HMODULE,&::FreeLibrary> RAIIHMODULE;
typedef Handle<HDESK,&::CloseDesktop> RAIIHDESKTOP;

如果有一个HANDLE不是由 type 的函数释放的BOOL(WINAPI)(HANDLE),那么你可能对此有疑问。如果释放函数仅因返回类型不同,您可以将其添加为模板参数并仍然使用此解决方案。

于 2013-05-21T19:16:00.593 回答
2

我相信所有 Windows 指针在技术上都是指向系统的 Windows 内核部分内部对象的指针(或者有时,可能是指向由内核代码分配的用户端对象,或该主题的某些变体)。

我不相信您应该将它们视为指针。它们只是纯粹技术角度的指针。它们不再是“指针”,而不是 C 风格的“FILE *”是指针。我不认为您会建议shared_ptr<FILE*>稍后使用 来处理关闭文件。

将句柄包装成稍后清理它的东西绝对是一个好主意,但我不认为使用智能指针解决方案是正确的解决方案。使用知道如何关闭句柄的模板系统将是理想的。

我想您还需要以某种适用于所有相关人员的好方式处理“我想将这个句柄从这里传递到其他地方” - 例如,您有一个以某种方式获取资源的函数,并将句柄返回给那些资源——你是否返回一个已经包装的对象,如果是,副本是如何工作的?

如果您需要在使用另一支笔之前保存一个手柄的副本(例如,保存当前笔,然后设置自定义笔,然后恢复)怎么办?

于 2013-05-21T22:38:10.097 回答
1

Technically, this ought to work perfectly well under all present versions of Windows, and it is hard to find a real reason against doing it (it is actually a very clever use of existing standard library functionality!), but I still don't like the idea because:

  1. Handles are pointers, and Handles are not pointers. Handles are opaque types, and one should treat them as such for compatibility. Handles are pointers, and it is unlikely that handles will ever be something different, but it is possible that this will change. Also, handles may or may not have values that are valid pointer values, and they may or may not have different values under 32bit and 64bit (say INVALID_HANDLE_VALUE), or other side effects or behaviours that you maybe don't foresee now. Assuming that a handle has certain given properties may work fine for decades, but it may (in theory) mysteriously fail in some condition that you didn't think about. Admittedly, it is very unlikely to happen, but still it isn't 100% clean.
  2. Using a smart pointer in this way doesn't follow the principle of least astonishment. Because, hey, handles aren't pointers. Having RAII built into a class that is named with an intuitive name ("Handle", "AutoHandle") will not cause anyone to raise an eyebrow.
于 2013-05-21T19:02:14.547 回答
1

我相信 unique_ptr 和 shared_ptr 都允许您提供自定义删除器。我相信这正是您正确管理句柄生命周期所需要的。

于 2013-05-21T20:28:00.887 回答