c++ - ¿El uso de ScopeGuard realmente conduce a un mejor código?
raii (7)
Me encontré con este artículo escrito por Andrei Alexandrescu y Petru Marginean hace muchos años, que presenta y analiza una clase de utilidad llamada ScopeGuard para escribir código de excepción segura. Me gustaría saber si la codificación con estos objetos realmente conduce a un mejor código o si ofusca el manejo de errores, ya que tal vez la devolución de llamada del guardia estaría mejor presentada en un bloque catch. ¿Alguien tiene alguna experiencia al usar estos en el código de producción real?
Definitivamente mejora tu código. Su afirmación tentativamente formulada, que es oscura y que el código merecería un bloque catch
, simplemente no es cierta en C ++ porque RAII es una expresión establecida. El manejo de recursos en C ++ se realiza mediante la adquisición de recursos y la recolección de elementos no utilizados se realiza mediante llamadas de destrucción implícitas.
Por otro lado, catch
bloques de catch
explícitos hincharían el código e introducirían errores sutiles porque el flujo del código se vuelve mucho más complejo y el manejo de los recursos debe hacerse explícitamente.
RAII (que incluye ScopeGuard
s) no es una técnica oscura en C ++, sino una mejor práctica firmemente establecida.
A menudo lo uso para proteger el uso de memoria, cosas que deben liberarse que fueron devueltas desde el sistema operativo. Por ejemplo:
DATA_BLOB blobIn, blobOut;
blobIn.pbData=const_cast<BYTE*>(data);
blobIn.cbData=length;
CryptUnprotectData(&blobIn, NULL, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &blobOut);
Guard guardBlob=guardFn(::LocalFree, blobOut.pbData);
// do stuff with blobOut.pbData
No he usado esta plantilla en particular, pero he usado algo similar antes. Sí, conduce a un código más claro cuando se compara con un código igualmente robusto implementado de diferentes maneras.
Sí.
Si hay una sola pieza de código C ++ que podría recomendar a cada programador de C ++ que pase 10 minutos aprendiendo, es ScopeGuard (ahora parte de la biblioteca Loki disponible gratuitamente).
Decidí intentar usar una versión (ligeramente modificada) de ScopeGuard para un programa de GUI Win32 más pequeño en el que estaba trabajando. Win32, como sabrá, tiene muchos tipos diferentes de recursos que deben cerrarse de diferentes maneras (por ejemplo, los identificadores de kernel generalmente se cierran con CloseHandle()
, GDI BeginPaint()
debe emparejarse con EndPaint()
, etc.) Utilicé ScopeGuard con todos estos recursos, y también para asignar buffers de trabajo con new
(por ejemplo, para conversiones de juegos de caracteres a / desde Unicode).
Lo que me sorprendió fue cuánto más corto era el programa. Básicamente, es un ganar-ganar: su código se hace más corto y más robusto al mismo tiempo. Los futuros cambios de código no pueden filtrar nada . Simplemente no pueden. ¿Cuan genial es eso?
Debo decir que no, que no. Las respuestas aquí ayudan a demostrar por qué es una idea realmente horrible. El manejo de recursos debe hacerse a través de clases reutilizables. Lo único que lograron con el uso de un protector de alcance es violar DRY el wazoo y duplicar el código de liberación de recursos en su base de código, en lugar de escribir una clase para manejar el recurso y eso es todo.
Si los protectores de alcance tienen algún uso real, el manejo de recursos no es uno de ellos. Son masivamente inferiores a RAII en ese caso, ya que RAII está deduplicado y las protecciones automáticas y de alcance son duplicados o fallas manuales.
Creo que las respuestas anteriores carecen de una nota importante. Como han señalado otros, puede usar ScopeGuard
para liberar recursos asignados independientemente de la falla (excepción). Pero puede que no sea lo único que desee usar con el protector de alcance. De hecho, los ejemplos del artículo vinculado usan ScopeGuard
para un propósito diferente: transcations. En resumen, podría ser útil si tiene varios objetos (incluso si esos objetos usan correctamente RAII) que necesita mantener en un estado que de alguna manera esté correlacionado. Si el cambio de estado de cualquiera de esos objetos da como resultado una excepción (que, supongo, generalmente significa que su estado no cambió), entonces todos los cambios ya aplicados deben revertirse. Esto crea su propio conjunto de problemas (¿y si también falla un rollback?). Podría intentar lanzar su propia clase que gestione dichos objetos correlacionados, pero a medida que la cantidad aumente se ensuciaría y probablemente recurriría a usar ScopeGuard
internamente de todos modos.
Sí.
Era tan importante en C ++ que incluso una sintaxis especial para él en D:
void somefunction() {
writeln("function enter");
// c++ has similar constructs but not in syntax level
scope(exit) writeln("function exit");
// do what ever you do, you never miss the function exit output
}