2010-03-12 21 views
15

Soy aficionado C++. Estoy escribiendo algunos códigos API de Win32 y hay identificadores y objetos asignados extrañamente de forma compuesta en abundancia. Entonces me preguntaba: ¿hay alguna clase de contenedor que facilite la administración de recursos? Por ejemplo, cuando quiero cargar algunos datos, abro un archivo con CreateFile() y obtengo un HANDLE. Cuando termine con esto, debo llamar al CloseHandle(). Pero para cualquier función de carga razonablemente compleja habrá docenas de posibles puntos de salida, sin mencionar excepciones.¿Qué clase de contenedor en C++ debo usar para la administración automatizada de recursos?

Así que sería genial si pudiera envolver el controlador en algún tipo de clase de contenedor que llamaría automáticamente al CloseHandle() una vez que la ejecución haya salido del alcance. Aún mejor: podría hacer un conteo de referencia para poder pasarlo dentro y fuera de otras funciones, y liberaría el recurso solo cuando la última referencia haya dejado el alcance.

El concepto es simple, pero ¿hay algo así en la biblioteca estándar? Estoy usando Visual Studio 2008, por cierto, y no quiero adjuntar un framework de terceros como Boost o algo así.

Respuesta

11

Escribe la tuya. Son solo unas pocas líneas de código. Es una tarea tan simple que no es si vale la pena para proporcionar una versión genérica reutilizable.

struct FileWrapper { 
    FileWrapper(...) : h(CreateFile(...)) {} 
    ~FileWrapper() { CloseHandle(h); } 

private: 
    HANDLE h; 
}; 

Piense en lo que es una versión genérica tendría que hacer: Tendría que ser parametrizable para que pueda especificar cualquier par de funciones, y cualquier número de argumentos a ellos. Simplemente crear una instancia de dicho objeto probablemente tomaría tantas líneas de código como la definición de clase anterior.

Por supuesto, C++ 0x podría inclinar la balanza un poco con la adición de expresiones lambda. Dos expresiones lambda podrían pasarse fácilmente a una clase contenedora genérica, por lo que una vez que C++ 0x soporta, podría ver una clase RAII genérica añadida a Boost o algo así.

Pero por el momento, es más fácil rodar el suyo siempre que lo necesite.

En cuanto a agregar recuento de referencias, desaconsejaría. El recuento de referencias es costoso (de repente su mango debe ser dinámicamente asignado, y los contadores de referencia deben mantenerse en cada asignación), y es muy difícil hacerlo bien.Es un área que rebosa condiciones de carrera sutiles en un entorno enhebrado.

Si haces necesitan recuento de referencias, simplemente hacer algo como boost::shared_ptr<FileWrapper>: Envuelva sus clases RAII ad-hoc personalizados en un shared_ptr.

+0

La mejor idea, en mi humilde opinión Estas clases se llaman protectores de manija ... – SadSido

+2

El código es malo ya que la estructura se puede copiar. Consulte http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization – Kerido

+2

@Kerido, tal vez, tal vez no. Depende de la semántica del recurso que está envolviendo. Creo es justo darle a jalf el beneficio de la duda y asumir que el código publicado es solo un simple ejemplo ilustrativo. –

0

MFC tiene algunas primitivas adecuadas (consulte CFile por ejemplo), pero no la biblioteca estándar.

+0

Tal clase no suena muy compleja. Tal vez hay un ejemplo de implementación en la web en algún lugar que podría copiar y pegar en mi solución. ¿Qué palabras clave debo usar en Google para eso? –

+0

Mire esto, por ejemplo: http://bbdsoft.com/win32.html Primera coincidencia para la consulta "CreateFile CloseHandle wrapper". – sharptooth

+0

También CFile y similares simplificarán mucho las cosas en comparación con escribir todo el código con Win32 sin formato. – sharptooth

2

Básicamente, fstream es una buena envoltura de C++ para manejadores de archivos. Es parte del estándar, lo que significa que es portátil, bien probado y extensible de una manera orientada a objetos. Para los recursos de archivos, es un gran concepto.

Sin embargo, fstream sólo funciona para archivos, no para las manijas genéricas, es decir, hilos, procesos, objetos de sincronización, archivos mapeados en memoria, etc.

+0

Solo usé manejadores de archivos como un ejemplo fácil de entender común. En la práctica las cosas son ... más extrañas. –

+0

¿Qué asas quiso decir entonces? – Kerido

+0

SSPI maneja como CredHandle, CtxtHandle y SecBufferDesc. La última es una estructura extraña que contiene una matriz de estructuras asignadas dinámicamente donde cada estructura tiene un puntero a un búfer asignado dinámicamente. En pocas palabras, es una colección de tamaño variable de búferes de tamaño variable. La función de liberación no es tan trivial como simplemente "eliminar". :( –

0

Visual C++ 2008 es compatible con TR1 a través del paquete de características, y TR1 incluye shared_ptr . Yo usaría esto: es una clase de puntero inteligente muy poderosa y se puede generalizar para hacer el tipo de gestión de recursos que está solicitando.

TR1 es efectivamente una extensión del estándar. Creo que todavía es oficialmente "pre-estándar", pero efectivamente puedes considerarlo bloqueado.

+0

Tenga en cuenta que con 'shared_ptr' para esto es necesario escribir una función de borrado personalizado en algunos casos. (En casos simples, simplemente podría pasar, por ejemplo, la función 'CloseHandle' como el eliminador.) – celticminstrel

0

No creo que hay algo en la librería estándar, y también duda de que compartía punteros (como en el impulso) se puede usar (ya que aquellos esperarían que el puntero MANEJA, NO MANIPULAR).

No debe ser difícil escribir uno usted mismo, siguiendo el idioma scope guard (y haciendo uso de plantillas/punteros a funciones, etc., si así lo desea).

0
template <typename Traits> 
class unique_handle 
{ 
    using pointer = typename Traits::pointer; 

    pointer m_value; 

    auto close() throw() -> void 
    { 
     if (*this) 
     { 
      Traits::close(m_value); 
     } 
    } 

public: 

    unique_handle(unique_handle const &) = delete; 
    auto operator=(unique_handle const &)->unique_handle & = delete; 

    explicit unique_handle(pointer value = Traits::invalid()) throw() : 
     m_value{ value } 
    { 
    } 

    unique_handle(unique_handle && other) throw() : 
     m_value{ other.release() } 
    { 
    } 

    auto operator=(unique_handle && other) throw() -> unique_handle & 
    { 
     if (this != &other) 
     { 
      reset(other.release()); 
     } 

     return *this; 
    } 

    ~unique_handle() throw() 
    { 
     close(); 
    } 

    explicit operator bool() const throw() 
    { 
     return m_value != Traits::invalid(); 
    } 

    auto get() const throw() -> pointer 
    { 
     return m_value; 
    } 

    auto get_address_of() throw() -> pointer * 
    { 
     ASSERT(!*this); 
     return &m_value; 
    } 

    auto release() throw() -> pointer 
    { 
     auto value = m_value; 
     m_value = Traits::invalid(); 
     return value; 
    } 

    auto reset(pointer value = Traits::invalid()) throw() -> bool 
    { 
     if (m_value != value) 
     { 
      close(); 
      m_value = value; 
     } 

     return static_cast<bool>(*this); 
    } 

    auto swap(unique_handle<Traits> & other) throw() -> void 
    { 
     std::swap(m_value, other.m_value); 
    } 
}; 

template <typename Traits> 
auto swap(unique_handle<Traits> & left, 
    unique_handle<Traits> & right) throw() -> void 
{ 
    left.swap(right); 
} 

template <typename Traits> 
auto operator==(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() == right.get(); 
} 

template <typename Traits> 
auto operator!=(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() != right.get(); 
} 

template <typename Traits> 
auto operator<(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() < right.get(); 
} 

template <typename Traits> 
auto operator>=(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() >= right.get(); 
} 

template <typename Traits> 
auto operator>(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() > right.get(); 
} 

template <typename Traits> 
auto operator<=(unique_handle<Traits> const & left, 
    unique_handle<Traits> const & right) throw() -> bool 
{ 
    return left.get() <= right.get(); 
} 

struct null_handle_traits 
{ 
    using pointer = HANDLE; 

    static auto invalid() throw() -> pointer 
    { 
     return nullptr; 
    } 

    static auto close(pointer value) throw() -> void 
    { 
     VERIFY(CloseHandle(value)); 
    } 
}; 

struct invalid_handle_traits 
{ 
    using pointer = HANDLE; 

    static auto invalid() throw() -> pointer 
    { 
     return INVALID_HANDLE_VALUE; 
    } 

    static auto close(pointer value) throw() -> void 
    { 
     VERIFY(CloseHandle(value)); 
    } 
}; 

using null_handle = unique_handle<null_handle_traits>; 
using invalid_handle = unique_handle<invalid_handle_traits>; 
+3

Lo mejor es agregar alguna descripción a su respuesta. – GMchris

Cuestiones relacionadas