之前在 用 unique_ptr 管理 Windows HANDLE 中介绍了如何基于 std::unique_ptr
快速实现对 Windows HANDLE 的 RAII 化。
但是如果我们也要对 fd
或者 Windows 上的 SOCKET
做类似的处理,重复实现 xx_handle
和 xx_handle_deleter
就显得过于琐碎。
实际上我们可以利用模板参数注入,将共同点抽象出来,不同点实现成各自的 type traits,然后利用模板进行注入。
这样一来,我们只需要简单实现每个 handle type 对应的基础属性,就可以直接复用核心实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 template <typename Traits>class handle_ptr {public : using handle_type = typename Traits::handle_type; handle_ptr () noexcept = default ; handle_ptr (std::nullptr_t ) noexcept {} handle_ptr (handle_type handle) noexcept : handle_ (handle) {} ~handle_ptr () = default ; explicit operator bool () const noexcept { return Traits::is_valid (handle_); } operator handle_type () const noexcept { return handle_; } friend bool operator ==(handle_ptr lhs, handle_ptr rhs) noexcept { return lhs.handle_ == rhs.handle_; } friend bool operator !=(handle_ptr lhs, handle_ptr rhs) noexcept { return !(lhs == rhs); } private : handle_type handle_{Traits::null_handle}; }; template <typename Traits>struct handle_ptr_deleter { using pointer = handle_ptr<Traits>; void operator () (pointer ptr) { Traits::close (ptr); } };
PS:不同于文章中的例子,基底类起名 handle_ptr
更为确切;因为在 unique_ptr
的内部会用来定义 pointer
;而几乎 unique_ptr 的语义行为都是围绕 pointer
来完成的。
有了上面这个基底之后,针对 fd-wrapper,我们只需要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct fd_traits { using handle_type = int ; static bool is_valid (handle_type handle) noexcept { return handle != null_handle; } static void close (handle_type handle) noexcept { ::close (handle); } static constexpr handle_type null_handle{-1 }; }; using fd_deleter = handle_ptr_deleter<fd_traits>;using unique_fd = std::unique_ptr<fd_traits::handle_type, fd_deleter>;inline unique_fd wrap_unique_fd (int raw_fd) { return unique_fd (raw_fd); }
这样一来,实现一个新的 unique handle wrapper,我们只需要确定四个东西:
handle type 的实际类型
null handle 的值,用来确定 default/zero initialized 之后 handle 状态
实现 is_valid()
实现 close()
剩下的就是常规的类型实例化。