对Windows HANDLE使用std :: unique_ptr

U00*_*07D 18 c++ unique-ptr visual-c++ c++11 visual-studio-2012

我试图使用std :: unique_ptrs以异常安全的方式管理Windows HANDLE.

首先我试过:

struct HandleDeleter
{
    void operator()( HANDLE handle )
    {
        if( handle )
        {
            FindVolumeClose( handle )
        }
    }
}
typedef std::unique_ptr< HANDLE, HandleDeleter > unique_vol_handle_t;
Run Code Online (Sandbox Code Playgroud)

稍后在我的代码中尝试使用它时:

unique_vol_handle_t volH( FindFirstVolumeW( buffer, MAX_GUID_PATH ) );

我从Visual Studio 2012RC收到以下错误:

1>          error C2664: 'std::unique_ptr<_Ty,_Dx>::unique_ptr(std::nullptr_t) throw()' : cannot convert parameter 1 from 'HANDLE' to 'std::nullptr_t'
1>          with
1>          [
1>              _Ty=HANDLE,
1>              _Dx=VolumeHandleDeleter
1>          ]
1>          nullptr can only be converted to pointer or handle types
Run Code Online (Sandbox Code Playgroud)

引用上面的volH声明行.

搜索了一段时间后,我发现了一篇博文,基本上说,添加:

typedef HANDLE pointer;

到结构声明的顶部,一切都会很好.

我不相信,但我尝试了它,它确实解决了错误.我很困惑如何定义一个类型(甚至没有引用它)可以产生这样的差异.

两个问题:

1)你能解释原始错误吗?我不明白为什么编译器指的是std::nullptr_t/nullptr.

2)typedef如何解决这个问题(或者至少看起来如此)?是否有一个较少的"远距离的幽灵行动"解决方案?

Lil*_*ard 25

执行unique_ptr检查一个的存在::pointer对删除器类型.如果删除器具有::pointer类型那么这种类型的被用作pointer上的typedef unique_ptr.否则,使用指向第一个模板参数的指针.

根据cppreference.com,unique_ptr::pointer类型定义为

std::remove_reference<D>::type::pointer 如果该类型存在,否则 T*


Som*_*ude 5

unique_ptr上MSDN手册:

存储的指向所拥有资源的stored_ptr指针具有类型指针.它是Del::pointer定义的,Type *如果没有定义的话.如果删除器是无状态的,则存储的删除器对象stored_deleter在对象中不占用空间.请注意,Del可以是引用类型.

这意味着如果你提供一个删除函子,它必须提供一个pointer用于实际指针类型的类型unique_ptr.否则它将是指向您提供的类型的指针,在您的情况下HANDLE*是不正确的.


Sam*_*ris 5

我一直在为 Windows 中的各种类型的句柄执行以下操作。假设我们已经在某处声明:

std::unique_ptr<void, decltype (&FindVolumeClose)> fv (nullptr, FindVolumeClose);
Run Code Online (Sandbox Code Playgroud)

这填充了:

HANDLE temp = FindFirstVolume (...);
if (temp != INVALID_HANDLE_VALUE)
    fv.reset (temp);
Run Code Online (Sandbox Code Playgroud)

无需声明单独的结构来包装删除器。因为HANDLE真的是void *unique_ptr需要void它的类型; 对于使用DECLARE_HANDLE宏的其他类型的句柄,可以避免这种情况:

// Manages the life of a HHOOK
std::unique_ptr<HHOOK__, decltype (&UnhookWindowsHookEx)> hook (nullptr, UnhookWindowsHookEx);
Run Code Online (Sandbox Code Playgroud)

等等。

  • 如果定义了 `STRICT`,则 `HANDLE` 不是 `void*`。你可以使用 `std::remove_pointer` 而不是硬编码类型,即 `std::remove_pointer&lt;HANDLE&gt;::type`、`std::remove_pointer&lt;HHOOK&gt;::type` 等,这样你总是无论是否定义了“STRICT”,都具有正确的类型 (2认同)
  • @RemyLebeau:“STRICT”将 GDI 和用户句柄(如“HBRUSH”、“HDC”、“HMONITOR”、“HINSTANCE”、“HKEY”等)转换为不同的类型。但是内核的`HANDLE`都是`void *`,即使是那些需要`CloseHandle`以外的东西来关闭它们的,比如从`FindFirstVolume`、`FindFirstFile`等返回的。 (2认同)

Lev*_*ell 5

使用std::unique_ptrWindows HANDLE 的正确(且安全)方法如下:

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

class WinHandle {
    HANDLE value_;
public:
    WinHandle(std::nullptr_t = nullptr) : value_(nullptr) {}
    WinHandle(HANDLE value) : value_(value == INVALID_HANDLE_VALUE ? nullptr : value) {}

    explicit operator bool() const { return value_ != nullptr; }
    operator HANDLE() const { return value_; }

    friend bool operator ==(WinHandle l, WinHandle r) { return l.value_ == r.value_; }
    friend bool operator !=(WinHandle l, WinHandle r) { return !(l == r); }

    struct Deleter {
        typedef WinHandle pointer;
        void operator()(WinHandle handle) const { CloseHandle(handle); }
    };
};

inline bool operator ==(HANDLE l, WinHandle r) { return WinHandle(l) == r; }
inline bool operator !=(HANDLE l, WinHandle r) { return !(l == r); }
inline bool operator ==(WinHandle l, HANDLE r) { return l == WinHandle(r); }
inline bool operator !=(WinHandle l, HANDLE r) { return !(l == r); }

typedef std::unique_ptr<WinHandle, WinHandle::Deleter> HandlePtr;
Run Code Online (Sandbox Code Playgroud)

这种方式INVALID_HANDLE_VALUE被隐式地视为 null,因此永远不会传递给CloseHandle函数,并且您永远不需要显式测试它。像平常一样使用std::unique_ptr's来代替:operator bool()

HandlePtr file(CreateFile(...));
if (!file) {
    // handle error
}
Run Code Online (Sandbox Code Playgroud)

编辑:我最初忘记这不是INVALID_HANDLE_VALUE 类型的唯一无效值HANDLE,事实上它被许多(如果不是大多数)内核函数视为有效的伪句柄。嗯,很高兴知道。