Std:: unique_ptr, удаления и API Win32

В VC2012 я хочу создать мьютекс в конструкторе с использованием уникального указателя и deleter, так что мне не нужно создавать деструктор только для вызова CloseHandle.

Я бы подумал, что это сработает:

struct foo
{
    std::unique_ptr<HANDLE, BOOL(*)(HANDLE)> m_mutex;
    foo() : m_mutex(CreateMutex(NULL, FALSE, NULL), CloseHandle) {}
}

но при компиляции я получаю сообщение об ошибке:

error C2664: 'std::unique_ptr<_Ty,_Dx>::unique_ptr(void *,int 
(__cdecl *const &)(HANDLE)) throw()' : cannot convert parameter 1 from 
'HANDLE' to 'void *'

Когда я изменяю конструктор таким образом:

foo() : m_mutex((void*)CreateMutex(NULL, FALSE, 
    (name + " buffer mutex").c_str()), CloseHandle) {}

Я получаю еще более необычное:

error C2664: 'std::unique_ptr<_Ty,_Dx>::unique_ptr(void *,
int (__cdecl *const &)(HANDLE)) throw()' : cannot convert 
parameter 1 from 'void *' to 'void *'

Сейчас я в недоумении. HANDLE - это typedef for void *: есть ли какая-нибудь магия преобразования, о которой мне нужно знать?

Ответ 1

Забудьте об пользовательском удалении на данный момент. Когда вы говорите std::unique_ptr<T>, конструктор unique_ptr ожидает получить T*, но CreateMutex возвращает a HANDLE, а не HANDLE *.

Есть три способа исправить это:

std::unique_ptr<void, deleter> m_mutex;

Вам нужно будет вернуть возвращаемое значение CreateMutex в void *.

Другой способ сделать это - использовать std::remove_pointer, чтобы перейти к HANDLE базовому типу.

std::unique_ptr<std::remove_pointer<HANDLE>::type, deleter> m_mutex;

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

struct mutex_deleter {
  void operator()( HANDLE h ) 
  {
    ::CloseHandle( h );
  }
  typedef HANDLE pointer;
};
std::unique_ptr<HANDLE, mutex_deleter> m_mutex;
foo() : m_mutex(::CreateMutex(NULL, FALSE, NULL), mutex_deleter()) {}

Теперь, если вы хотите передать указатель на тип функции в качестве делетера, тогда при работе с Windows API вам также нужно обратить внимание на вызывающее соглашение при создании указателей на функции.

Итак, указатель на CloseHandle должен выглядеть так:

BOOL(WINAPI *)(HANDLE)

Объединив все это,

std::unique_ptr<std::remove_pointer<HANDLE>::type, 
                BOOL(WINAPI *)(HANDLE)> m_mutex(::CreateMutex(NULL, FALSE, NULL),
                                                &::CloseHandle);

Мне легче использовать лямбда вместо

std::unique_ptr<std::remove_pointer<HANDLE>::type, 
                void(*)( HANDLE )> m_mutex;
foo() : m_mutex(::CreateMutex(NULL, FALSE, NULL), 
                []( HANDLE h ) { ::CloseHandle( h ); }) {}

Или, как было предложено @hjmd в комментариях, используйте decltype для вывода типа указателя функции.

std::unique_ptr<std::remove_pointer<HANDLE>::type, 
                decltype(&::CloseHandle)> m_mutex(::CreateMutex(NULL, FALSE, NULL),
                                                  &::CloseHandle);

Ответ 2

Другие указали, как работает целая проблема HANDLE/HANDLE*. Здесь гораздо более умный способ справиться с этим, используя интересные функции std::unique_pointer.

struct WndHandleDeleter
{
  typedef HANDLE pointer;

  void operator()(HANDLE h) {::CloseHandle(h);}
};

typedef std::unique_ptr<HANDLE, WndHandleDeleter> unique_handle;

Это позволяет unique_handle::get возвращать HANDLE вместо HANDLE* без каких-либо причудливых std::remove_pointer или других подобных вещей.

Это работает, потому что HANDLE является указателем и поэтому удовлетворяет NullablePointer.

Ответ 3

Проблема заключается в том, что вы фактически определяете unque_ptr, который содержит указатель на дескриптор (HANDLE *), но вы передаете только HANDLE, а не указатель на него.