Подтвердить что ты не робот

Использование std:: unique_ptr для Windows HANDLE

Я пытаюсь использовать 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;

Позже в моем коде, когда я пытаюсь использовать его:

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

ссылаясь на строку объявления volH, сразу выше.

После поиска в течение некоторого времени я нашел статью в блоге, в которой, в основном, сказано:

typedef HANDLE pointer;

к началу объявления структуры, и все будет хорошо.

Я не поверил, но я попробовал, и это действительно помогло исправить ошибку. Я озадачен тем, как определение типа (даже без ссылки на него) может иметь такое значение.

Два вопроса:

1) Можете ли вы объяснить оригинальную ошибку? Я не понимаю, почему компилятор ссылается на std::nullptr_t/nullptr.

2) Как typedef разрешает это (или, по крайней мере, кажется)? Есть ли менее пугающее решение на расстоянии?

4b9b3361

Ответ 1

Реализация unique_ptr проверяет наличие типа ::pointer на удалитель. Если средство удаления имеет тип ::pointer тогда этот тип используется в качестве pointer typedef для unique_ptr. В противном случае используется указатель на первый аргумент шаблона.

Согласно cppreference.com, тип unique_ptr unique_ptr::pointer определяется как

std::remove_reference<D>::type::pointer если этот тип существует, в противном случае T*

Ответ 2

Из руководства MSDN по unique_ptr:

Сохраненный указатель на принадлежащий ему ресурс stored_ptr имеет указатель на тип. Это Del::pointer, если определено, и Type *, если нет. Сохраненный объект deleter stored_deleter не занимает места в объекте, если deleter не имеет состояния. Обратите внимание, что Del может быть ссылочным типом.

Это означает, что если вы предоставляете функтор deleter, он должен предоставить тип pointer, который используется для фактического типа указателя unique_ptr. В противном случае это будет указатель на ваш предоставленный тип, в вашем случае HANDLE*, что неверно.

Ответ 3

Я рекомендую вам взглянуть на Предложение добавить дополнительные обтекатели RAII в стандартную библиотеку и недостатки использования интеллектуальных указателей с ручками.

Лично я использую эталонную реализацию этого предложения вместо использования std::unique_ptr для этих ситуаций.

Ответ 4

Я делал следующее для различных типов ручек в Windows. Предполагая, что мы где-то заявили:

std::unique_ptr<void, decltype (&FindVolumeClose)> fv (nullptr, FindVolumeClose);

Это заполняется:

HANDLE temp = FindFirstVolume (...);
if (temp != INVALID_HANDLE_VALUE)
    fv.reset (temp);

Не нужно объявлять отдельную структуру для обертки удалений. Так как HANDLE действительно a void *, то unique_ptr принимает void в качестве своего типа; для других видов ручек, которые используют макрос DECLARE_HANDLE, этого можно избежать:

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

И так далее.

Ответ 5

Правильный (и безопасный) способ использования std::unique_ptr для Windows HANDLEs выглядит примерно так:

#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;

Этот способ INVALID_HANDLE_VALUE неявно рассматривается как null и поэтому никогда не будет передан функции CloseHandle, и вам не нужно явно проверять его. Вместо этого используйте std::unique_ptr operator bool(), как обычно:

HandlePtr file(CreateFile(...));
if (!file) {
    // handle error
}

EDIT: Я изначально забыл, что INVALID_HANDLE_VALUE не единственное недопустимое значение для типа HANDLE и что на самом деле он рассматривается как действительный псевдо-дескриптор многими, если не большинством, функциями ядра. Хорошо, хорошо знать.

Ответ 6

В то время как @Levi Haskell его ответ принимает INVALID_HANDLE_VALUE, он не принимает во внимание, что 0 является допустимым значением дескриптора и рассматривает INVALID_HANDLE_VALUE и 0 (или nullptr) как то же самое. Это неверно:

Мое предложение (основано на Леви Хаскелле, его код так за него кредитует)

template<void *taInvalidHandleValue>
class cWinHandle 
{
    HANDLE value_ { taInvalidHandleValue };
public:
    cWinHandle() = default;
    cWinHandle(HANDLE value) : value_(value) {}
    ~cWinHandle()
    {
        reset();
    }

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

    HANDLE get() const { return value_; }
    HANDLE release() { HANDLE const result{ value_ }; value_ = taInvalidHandleValue; return result; }
    friend bool operator ==(cWinHandle l, cWinHandle r) { return l.value_ == r.value_; }
    friend bool operator !=(cWinHandle l, cWinHandle r) { return !(l == r); }
    void reset ()
    {
        if (value_ != taInvalidHandleValue)
        {
            CloseHandle(value_);
            value_ = taInvalidHandleValue;
        }
    }
};

inline bool operator ==(HANDLE l, cWinHandle<nullptr> r) { return cWinHandle<nullptr>(l) == r; }
inline bool operator !=(HANDLE l, cWinHandle<nullptr> r) { return !(l == r); }
inline bool operator ==(cWinHandle<nullptr> l, HANDLE r) { return l == cWinHandle<nullptr>(r); }
inline bool operator !=(cWinHandle<nullptr> l, HANDLE r) { return !(l == r); }
inline bool operator ==(HANDLE l, cWinHandle<INVALID_HANDLE_VALUE> r) { return cWinHandle<INVALID_HANDLE_VALUE>(l) == r; }
inline bool operator !=(HANDLE l, cWinHandle<INVALID_HANDLE_VALUE> r) { return !(l == r); }
inline bool operator ==(cWinHandle<INVALID_HANDLE_VALUE> l, HANDLE r) { return l == cWinHandle<INVALID_HANDLE_VALUE>(r); }
inline bool operator !=(cWinHandle<INVALID_HANDLE_VALUE> l, HANDLE r) { return !(l == r); }