用 unique_ptr 管理 Windows HANDLE
作为一个曾经的资深 Windows developer,Windows HANDLE
一个不引人注意的但是绝对值得引起注意的地方是,它的无效值表示是不唯一的。
除了最常见的 NULL
之外还有一个 INVALID_HANDLE_VALUE
,并且即使是在数值上这两个东西也不一样。
如果要为 HANDLE
写一个 C++ RAII Wrapper 那么必须要考虑这个点,比如 Chromium 就是这么做的。
Chromium 的这个设施早在 C++ 11 之前就有了,并且经过多年来的迭代已经变得异常复杂;如果你自己需要这样一个东西,又无法从 Chromium 这种工业项目中抽取一个组件的话,只能考虑自己实现。
但是要写对一个工业强度的 handle-wrapper 并不容易,同时还要考虑到通用性和实用性,比如可能后续要支持 fd/socket,Windows HDC 等各种奇奇怪怪的东西。
所以能否用 std::unique_ptr
来作为基础实现,针对一些特定细节做泛化?
这就引出一个问题:如果希望用 std::unique_ptr
来作为 HANDLE
的一个 RAII 包装,如何正确判断一个 handle 是否 valid?
更进一步,问题还可以变为:如何用 std::unique_ptr
管理 pointer-like 的资源?
答案是 std::unique_ptr
在设计上还真考虑到了这种场景:
Unlike
std::shared_ptr
,std::unique_ptr
may manage an object through any custom handle type that satisfiesNullablePointer
.
std::unique_ptr
允许通过内部的 custom handle 来管理对象资源,只要这个 custom handle 满足 NullablePointer
。
根据 https://en.cppreference.com/w/cpp/memory/unique_ptr 里的 _Member Types_,可以发现,只要 Deleter::pointer
是有定义的,std::unique_ptr::pointer
就会定义为 Deleter::poiner
,否则才是 T*
。
而 NullablePoiner 则定义了 custom handle object 和 std::nullptr_t
的关系,并且文档里给了一个 minimalistic 的实现。
所以我们只需要
给
HANDLE
先做一个win_handle
的 custom handle class,并且这个 class 满足 nullable-pointer requirements;同时正确实现explicit operator bool()
定义一个
win_handle_deleter
,内部定义了 pointer 为win_handle
和
std::unique_ptr
组合在一起
大概长这样:
1 | class win_handle { |
随便找个测试框架或者新建工程测试一下:
1 | TEST_CASE("For Windows Handle", "[windows-handle]") { |
其他资源也可以用类似的手法进行二次包装处理。
- 关于为什么
HANDLE
会有NULL
和INVALID_HANDLE_VALUE
,参阅考据帝 Raymond Chen 的这篇文章 - 上面文章链接里面也提到了一个更“可怕”的事情:
GetCurrentProcess()
返回的 pseudo-handle 在数值上恰好等于INVALID_HANDLE_VALUE
,但是在语义上,pseudo-handle 是 valid 的。