2013-02-12 30 views
22

In VC2012, desidero creare un mutex in un costruttore utilizzando un puntatore univoco e un deleter, in modo che non sia necessario creare un distruttore per chiamare CloseHandle.std :: unique_ptr, deleters e API Win32

avrei pensato che questo dovrebbe funzionare:

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

ma sulla compilazione ottengo un errore:

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

Quando ho modificare il costruttore così:

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

I ancora più insolito:

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

Sono in perdita ora. HANDLE è un typedef per void *: c'è qualche magia di conversione che devo sapere?

+0

V'è una buona risposta alla tua domanda qui sotto, ma vorrei seriamente in considerazione rotazione mia classe di appartenenza personalizzata per un mutex, piuttosto che sfruttando deleter di std :: unique_ptr per questo scopo. Il fatto che un HANDLE sia un puntatore è un dettaglio di implementazione. Potrebbe facilmente essere stato un indice o un altro valore magico. Crea il tuo wrapper RAII per questo e lascia unique_ptr per gestire i puntatori "reali". –

+0

@Adrian: Vedo il tuo punto. Potrebbe anche contenere l'accoppiamento RAI Wait/Release nel costruttore/distruttore. Saluti. – hatcat

risposta

40

Dimentica il deleter personalizzato per ora. Quando si dice std::unique_ptr<T>, il costruttore unique_ptr si aspetta di ricevere un T*, ma CreateMutex restituisce un HANDLE, non un HANDLE *.

Ci sono 3 modi per risolvere questo:

std::unique_ptr<void, deleter> m_mutex; 

dovrete lanciare il valore di ritorno di CreateMutex ad un void *.

Un altro modo per farlo è utilizzare std::remove_pointer per accedere al tipo sottostante di HANDLE.

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

Un altro modo per fare questo è quello di sfruttare il fatto che se deleter s' il unique_ptr contiene un tipo annidato denominato pointer, allora il unique_ptr utilizzerà quel tipo per il suo puntatore oggetto gestito anziché 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()) {} 

Ora, se si vuole passare un puntatore a funzione di tipo come il deleter, poi quando si tratta con l'API di Windows è inoltre necessario prestare attenzione alla convenzione di chiamata durante la creazione di puntatori a funzione.

Quindi, un puntatore a funzione a CloseHandle deve assomigliare a questo

BOOL(WINAPI *)(HANDLE) 

Combinando tutto questo,

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

trovo più facile da usare un lambda invece

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

O come suggerito da @hjmd nei commenti, usa decltype per dedurre e il tipo del puntatore della funzione.

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

+1 Invece del puntatore della funzione basta usare decltype (& CloseHandle) – hmjd

+1

Un'espressione lambda produce un valore, non un tipo. Non vedo come verrà compilato l'esempio di lambda. – GManNickG

+0

@GManNickG Hai ragione, risolto. Grazie! – Praetorian

1

Il problema è che in effetti si definisce unque_ptr che tiene puntatore a gestire (MANICO *) tipo, ma si passa solo maniglia, non puntatore ad esso.

+0

La risposta di Praetorian è abbastanza completa e corretta, IHMO. Scritto nello stesso momento in cui stavo scrivendo. –

28

Altri hanno sottolineato come funziona l'intero problema HANDLE/HANDLE*. Ecco un modo molto più intelligente per affrontarlo, utilizzando le interessanti funzionalità di std::unique_pointer.

struct WndHandleDeleter 
{ 
    typedef HANDLE pointer; 

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

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

Questo permette unique_handle::get per tornare HANDLE invece di HANDLE*, senza alcuna fantasia std::remove_pointer o altre cose del genere.

Questo funziona perché HANDLE è un puntatore e quindi soddisfa NullablePointer.

+0

Sono un po 'lacerato: mi piace l'ultima riga della risposta di Praetorian poiché l'intera soluzione è locale, ma mi piace la tua soluzione in quanto è un buon riutilizzo. Ti darò un +1! Grazie. – hatcat

+0

+1. Presumo che tu possa usare una struttura anonima anche qui? –

+0

@ MahmoudAl-Qudsi: Non so perché vorresti, ma suppongo che potresti dichiarare una struttura senza nome, ma con una dichiarazione di variabile. Quindi usa 'decltype' per ottenere il suo tipo. Ma non stai guadagnando nulla, dal momento che in entrambi i casi stai inserendo un nome nello spazio dei nomi. –

Problemi correlati