2012-08-29 14 views
14

Estoy intentando usar std :: unique_ptrs para administrar Windows HANDLEs de una manera segura.Uso de std :: unique_ptr para Windows HANDLEs

Primero intentó:

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

Más adelante en mi código cuando trato de usarlo:

unique_vol_handle_t volH(FindFirstVolumeW(buffer, MAX_GUID_PATH));

me sale el siguiente error de 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 

haciendo referencia a la línea de declaración volH, inmediatamente arriba.

Después de buscar durante algún tiempo, encontré a blog article que básicamente dice, añadir:

typedef HANDLE pointer;

a la parte superior de la declaración struct, y todo estará bien.

No lo creía, pero lo probé y resolvió el error. Me sorprende cómo definir un tipo (sin siquiera hacer referencia a él) podría marcar una gran diferencia.

dos preguntas:

1) ¿Puede explicar el error original? No entiendo por qué el compilador se refiere al std::nullptr_t/nullptr.

2) ¿Cómo es que el typedef resuelve esto (o al menos parece)? ¿Existe una solución menos "escalofriante de acción a distancia" para esto?

+0

[Esta pregunta] (http://stackoverflow.com/questions/12066721/what-are- the-uses-of-the-type-stdnullptr-t). podría ayudar con su pregunta sobre 'nullptr_t'. –

+0

Gracias, Joachim. Eso ayuda con los mensajes nullptr. – U007D

+1

+1 para la referencia de mecánica cuántica – Jon

Respuesta

20

La implementación de unique_ptr comprueba la presencia de un tipo ::pointer en el eliminador. Si el eliminador tiene un tipo ::pointer, este tipo se utiliza como pointer typedef en unique_ptr. De lo contrario, se utiliza un puntero al primer argumento de la plantilla.

Según cppreference.com, el tipo unique_ptr::pointer se define como

std::remove_reference<D>::type::pointer si existe ese tipo, de lo contrario T*

+1

Gracias, Kevin por la explicación clara. Y al no definir el tipo de puntero, básicamente le digo al autorretrato que use HANDLE * (void **) en lugar de HANDLE (void *), lo cual es incorrecto. El compilador me dio el mensaje de error más críptico del mundo, pero tu explicación está en inglés, así que estoy aceptando tu respuesta, ¡gracias! – U007D

+0

¿Eso es útil? El estándar dice que 'operator *' devuelve '* get()', pero tiene un tipo de retorno 'T &', y 'get()' tiene un tipo de retorno 'pointer'. –

+0

Para la mayoría de los identificadores 0 es un valor válido y INVALID_HANDLE_VALUE es el valor no válido, por lo que esta solución no es correcta para la mayoría de los manejadores porque el restablecimiento() de unique_ptr buscará nullptr en lugar de INVALID_HANDLE_VALUE, por lo que debe crear su propia clase contenedora. – Air2

4

Desde el MSDN manual on unique_ptr:

El puntero almacenado para un recurso propio, stored_ptr tiene puntero de tipo. Es Del::pointer si está definido, y Type * si no es así. El borrado objeto stored_deleter no ocupa espacio en el objeto si el eliminador es apátrida. Tenga en cuenta que Del puede ser un tipo de referencia.

Esto significa que si usted proporciona un funtor Deleter se tiene que proporcionar un tipo pointer que se utiliza para el tipo de puntero real del unique_ptr. De lo contrario, será un puntero al tipo proporcionado, en su caso HANDLE* que no es correcto.

+0

Hola, Joachim. La explicación de Kevin anterior es más clara para mí que el lenguaje de MSDN, así que acepté su respuesta. Pero esto tiene sentido ahora. ¡Gracias! – U007D

3

Le recomiendo que eche un vistazo a A Proposal to Add additional RAII Wrappers to the Standard Library y al the drawbacks of using smart pointers with handles.

Personalmente, estoy haciendo uso de la implementación de referencia de esa propuesta en lugar de usar std::unique_ptr para estas situaciones.

+0

no puedo decir que estoy totalmente de acuerdo con los 'inconvenientes del uso de punteros inteligentes con asas' premisa, pero aprecio los enlaces a artículos interesantes y estimulantes tales como esto. Gracias. – U007D

2

He estado haciendo lo siguiente para varios tipos de identificadores en Windows. Suponiendo que hemos declarado en alguna parte:

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

Esto se completa con:

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

No hay necesidad de declarar una estructura separada para envolver los eliminadores. Como HANDLE es realmente un void *, unique_ptr toma void como su tipo; para otros tipos de mangos, que utilizan la macro DECLARE_HANDLE, esto se puede evitar:

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

Y así sucesivamente.

1

La forma correcta (y seguro) para utilizar std::unique_ptr para Windows maneja es algo como esto:

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

De esta manera INVALID_HANDLE_VALUE se trata implícitamente como nula, y por lo tanto nunca será pasado a CloseHandle función y que nunca necesidad de probarlo explícitamente. Uso std::unique_ptr 's operator bool() lugar, como lo haría normalmente:

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

EDIT: me olvidó en un principio que INVALID_HANDLE_VALUEis not the only invalid value para el tipo HANDLE, y que de hecho se trata como un valid pseudo handle por muchos, si no la mayoría , funciones del kernel. Bueno, es bueno saberlo.

-1

Mientras @Levi Haskell su respuesta toma INVALID_HANDLE_VALUE en cuenta, no tiene en cuenta que 0 es un valor de identificador válido y trata INVALID_HANDLE_VALUE y 0 (o nullptr) como el mismo. Esto no es correcto:

Mi sugerencia (basado en Levi Haskell su código para créditos a él)

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); } 
Cuestiones relacionadas