2010-06-01 24 views
13

Estoy escribiendo un software reactivo, que recibe repetidamente la entrada, la procesa y emite resultados relevantes. El bucle principal se ve algo como:Alternativa a C++ excepción

initialize(); 
while (true) { 
    Message msg,out; 
    recieve(msg); 
    process(msg,out); 
    //no global state is saved between loop iterations! 
    send(out); 
} 

Quiero que todo lo que ha producido un error durante la fase de proceso de alta whetehr es error de falta de memoria, error lógico, la afirmación no es válida, etc, el programa va a limpiar lo que hizo, y sigue corriendo. Asumiré que es una entrada inválida, y simplemente lo ignoraré.

La excepción de C++ es excepcionalmente buena para esa situación, podría rodear process con la cláusula try/catch, y lanzar una excepción cada vez que algo vaya wrog. Lo único que necesito para asegurarme de limpiar todos mis recursos antes de lanzar una excepción. Esto podría ser verificado por RAII, o escribiendo un asignador de recursos global (por ejemplo, si su destructor podría arrojar una excepción), y utilícelo exclusivamente para todos los recursos.

Socket s = GlobalResourceHandler.manageSocket(new Socket()); 
... 
try { 
    process(msg,out); 
catch (...) { 
    GlobalResourceHandler.cleanUp(); 
} 

Sin embargo, el uso de excepción está prohibido en nuestro estándar de codificación (también en Google's C++ standard por cierto), como resultado de todo el código se compila con excepciones fuera, y creo que nadie va a cambiar la manera de trabajar todo sólo por mi problema de diseño.

Además, este es el código de la plataforma integrada, por lo que cuanto menor sea la función extra de C++ que usemos, más rápido se vuelve el código, y más portátil es.

¿Existe un diseño alternativo que pueda considerar?

actualización: agradezco la respuesta de everyones sobre el código idiota estándar. Lo único que puedo decir es que, en las grandes organizaciones, debes tener reglas estrictas y, a veces, ilógicas, para asegurarte de que ningún idiota vendrá y no podrá mantener tu buen código. El estándar es más sobre las personas que sobre tecnicismos. Sí, el hombre malo puede hacer que cada código sea un desastre, pero es mucho peor si le das herramientas adicionales para la tarea.

Todavía estoy buscando un técnico respuesta.

+3

¿Podrían presentar referencia a la norma ++ C del Google que prohíbe excepciones? –

+7

La forma correcta de hacerlo con excepciones no es a través del asignador global, sino con el uso de objetos RAII (http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization). –

+2

@Pavel: triste pero cierto, http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions –

Respuesta

7

Codificando este tipo de servicios todo el día entiendo su problema. Aunque tenemos excepciones dentro de nuestro código , no los devolvemos a las bibliotecas externas que lo invocan, sino que tenemos un simple 'tribool'.

enum ReturnCode 
{ 
    OK = 0, // OK (there is a reason for it to be 0) 
    KO,  // An error occurred, wait for next message 
    FATAL // A critical error occurred, reboot 
}; 

Debo decir que FATAL es ... excepcional. No hay ninguna ruta de código en la aplicación que la devuelve, aparte de la inicialización (no puede hacer mucho si no se inicializa correctamente).

C++ aquí trae mucho con RAII, ya que se ríe de múltiples caminos de retorno y garantiza la liberación determinística de los objetos que contiene.

Para la comprobación de código real, sólo tiene que utilizar algunas macros:

// Here is the reason for OK being 0 and KO and Fatal being something else 

#define CHECK_RETURN(Expr) if (ReturnCode code = (Expr)) return code; 

#define CHECK_BREAK(Expr) if (ReturnCode code = (Expr)) \ 
    if (KO == code) break; else return code; 

A continuación, puede utilizarlos de esta manera:

CHECK_RETURN(initialize()) 
while(true) 
{ 
    Message msg,out; 
    CHECK_BREAK(receive(msg)) 
    CHECK_BREAK(process(msg,out)) 
    CHECK_BREAK(send(out)) 
} 

Como se ha señalado, el verdadero fastidio se trata de constructores. No puede tener constructores "normales" con tal situación.

Quizás pueda usar boost::optional, si no puede, realmente sugeriría duplicar la funcionalidad. Combine eso con funciones de fábrica sistémicos en lugar de constructores y que está libre para ir:

boost::optional<MyObject> obj = MyObject::Build(1, 2, 3); 
if (!obj) return KO; 

obj->foo(); 

se parece mucho a un puntero, excepto que está asignado pila y por lo tanto implica cerca de cero por encima.

+0

¡Gracias! Eso es lo que estaba buscando. De hecho, pensé en algo de esta forma. Mi único problema son las funciones que devuelve resultados. No es tan agradable convertir todo código 'if (isPolygonClockWise (poly) && isPolygonSimple (poly))' a 'bool simple, cw; CHECK_RETURN (isPolygonSimple (poly, & simple)); CHECK_RETURN (isPolygonClockWise (poly, &cw)); if (simple && cw)' –

+0

Recuerda que la mayoría las funciones no arrojan nada, si valida de primera mano (o incluso mejor, asegúrese de que poly es siempre válido) entonces esas 2 funciones nunca deberían necesitar arrojarse y están perfectamente bien devolver un booleano. –

0

Si se está ejecutando bajo Windows, podría usar excepciones SEH. También tienen la ventaja del controlador pre-stack-unwind que puede detener el desenrollado (EXCEPTION_CONTINUE_EXECUTION).

+1

Me imagino que si C++ no está permitido, entonces el de SEH tampoco lo será, especialmente teniendo en cuenta que no limpiarán la pila a menos que también haya habilitado el manejo de excepciones de C++. –

0

En la parte superior de mi cabeza, es posible que puedas lograr algo similar con las señales.

Configure un manejador de señal para captar las señales apropiadas y hacer que la limpieza funcione. Por ejemplo, si su código genera un SIGSEGV como resultado de algo que de otra manera habría arrojado una excepción un poco antes, puede intentar atraparlo con el manejador de señal.

Puede haber más que esto ya que no lo he pensado bien.

Espero que esto ayude.

+0

No creo que realmente quiera detectar infracciones de segmentación. Es completamente posible que el montón o pila esté dañado. – JeremyP

0

¿Llamas a cualquier biblioteca que pueda generar excepciones? Si es el caso, necesitarás una captura de prueba de todos modos. Para sus errores internos, cada método deberá devolver un código de error (use return solo para el código de error, use los parámetros de referencia para devolver los valores reales). Si desea que la limpieza de la memoria sea 100% confiable, puede iniciar su aplicación usando una aplicación de monitor. Si la aplicación falla, el monitor la iniciará de nuevo. Aún necesita cerrar los archivos y la conexión de DB, aunque.

6

Si no puede hacer una excepción a throw, entonces la alternativa es return (o al return false o código de error similar).

Ya sea que arroje o regrese, aún utiliza los destructores deterministas de C++ para liberar recursos.

Lo único que no puede simplemente 'devolver' es un constructor. Si tiene un error irrecuperable en un constructor, entonces es un buen momento para tirar; pero si no puede tirar, debe volver y, en ese caso, necesita otra señal para indicar un error de construcción:

  • Tiene constructores privados y métodos estáticos de fábrica; hacer que el método de fábrica devuelva nulo en falla de construcción; no olvide comprobar un retorno nulo cuando llame a un método de fábrica
  • Tenga una propiedad "get_isConstructedOk()" que invoque después de cada constructor (y no olvide invocar/verificar en cada objeto recién construido))
  • Implementa la construcción de 'dos ​​etapas': en la que dices que cualquier código que pueda fallar no debe estar en un constructor, y debe estar en un método separado bool initialize() que se llama después del constructor (y no olvides para llamar al initialize y no olvide verificar su valor de retorno).
4

El hecho de que el uso de excepciones está prohibido en las normas de codificación actuales, no significa que deba descartarlas para futuros problemas como este. Puede darse el caso de que sus estándares actuales de codificación no prevean que surja tal escenario. Si lo hicieran, probablemente te darían ayuda sobre cuál sería la implementación alternativa.

Esto me suena como un buen momento para desafiar sus estándares actuales de codificación. Si las personas que los escribieron aún están allí, hábleles directamente, ya sea que podrán responder a su pregunta en cuanto a estrategias alternativas o aceptarán que este es un caso de uso válido para excepciones.

+4

Puede que se sorprenda, pero muchas plataformas ni siquiera admiten excepciones ... por lo que esta es una pregunta legítima que merece una respuesta constructiva. –

+3

@kotlinski: Estoy diciendo que la no utilización de excepciones debe estar justificada. Si la plataforma objetivo de la OP no los admite, ¡esa es una buena justificación! Sin embargo, sospecho que el OP lo habría expresado así en vez de decir que sus pautas de codificación no se lo permitían. – Troubadour

+0

Esto me parece una respuesta constructiva. ¿Qué pasa si no apuntan a nada más que a Win32, y los desarrolladores originales eran viejos y no querían nada de este nuevo negocio de excepción del que todos los chicos están hablando en estos días? Por otra parte, puede estar en lo cierto, quizás no todas sus plataformas de destino admiten excepciones, o tienen problemas para lanzar excepciones a través de los límites de dll o lo que sea. Hablar con las personas que desarrollaron los estándares definitivamente ayudaría a explicar por qué sus estándares dirían esto. –

4

Sin embargo, el uso de la excepción está prohibido en nuestro estándar de codificación (también en el estándar de C++ de C++ por cierto). ¿Hay un diseño alternativo que pueda considerar?

Estándares de codificación como esos son nueces.

Sugiero que le pregunte a la persona/personas que desarrollaron y ordenaron ese estándar cómo resolver su problema. Si no tienen una buena respuesta, utilícela como justificación para ignorar esa parte del estándar de codificación ... con el permiso de sus jefes, por supuesto.

+0

¡Pero hay una buena justificación! Por ejemplo, Google menciona el hecho de que mezclar código de excepción con código que contiene excepciones podría ser problemático.Por ejemplo, el uso de excepciones nos obligará a cambiar los conmutadores de compilación para todo el proyecto y no estoy seguro de que valga la pena. Grrr ... No debería haber mencionado el código estándar en absoluto. Desvía la discusión del material técnico. –

+0

@Elazar: no hay ninguna justificación para prohibir abiertamente una construcción que hace cosas que son demasiado difíciles de corregir en otras formas. Si las excepciones se usan adecuadamente, están bien. El estándar debe enfocarse en establecer precondiciones/advertencias. –

4

Sin embargo, el uso de excepción está prohibido en nuestro estándar de codificación (también en estándar de Google C++ por cierto). ¿Hay un diseño alternativo que pueda considerar?

respuesta corta es sin.

Respuesta larga :). Puede hacer que todas las funciones devuelvan un código de error (similar a la implementación de la plataforma COM de Microsoft.

Las principales desventajas de este método son:

  • usted tiene que manejar todos los casos excepcionales expresamente

  • su tamaño aumenta de código de forma espectacular

  • el código se vuelve más difícil de leer.

En lugar de:

initialize(); 
while (true) { 
    Message msg,out; 
    recieve(msg); 
    process(msg,out); 
    //no global state is saved between loop iterations! 
    send(out); 
} 

tiene:

if(!succeedded(initialize())) 
    return SOME_ERROR; 

while (true) { 
    Message msg,out; 
    if(!succeeded(RetVal rv = recieve(msg))) 
    { 
     SomeErrorHandler(rv); 
     break; 
    } 
    if(!succeeded(RetVal rv = process(msg,out))) 
    { 
     SomeErrorHandler(rv); 
     break; 
    } 
    //no global state is saved between loop iterations! 
    if(!succeeded(RetVal rv = send(out))) 
    { 
     SomeErrorHandler(rv); 
     break; 
    } 
} 

Por otra parte, la puesta en práctica todas sus funciones tendrá que hacer lo mismo: rodear cada llamada a la función con una if.

En el ejemplo anterior, también debe decidir si el valor rv de cada iteración constituye un error para la función actual y (eventualmente) devolverlo directamente desde while, o interrumpir el error y devolver el valor.

En resumen, a excepción de posiblemente usar RAII en su código y plantillas (¿puede utilizarlas?), Termina cerca de "C code, using the C++ compiler".

Su código transforma cada función de un dos líneas en un ocho líneas, y así sucesivamente. Puede mejorar esto con el uso de funciones adicionales y macros #define d, pero las macros tienen su propia idiosincrasia con la que debe tener mucho cuidado.

En resumen, sus estándares de codificación hacen que su código sea innecesariamente más largo, más propenso a errores, más difícil de entender y más difícil de mantener.

Este es un buen caso para presentar a quien está a cargo de los estándares de codificación en su empresa :(

Editar: También puede implementar esto con señales pero son un mal sustituto de excepciones: se haga lo mismo que las excepciones, solo que también desactivan RAII por completo y hacen que su código sea aún menos elegante y más propenso a errores

+0

No puede hacer que los constructores devuelvan un código de error: los constructores no tienen código de retorno. – ChrisW

+1

No, no puedes. Al menos no directamente. Sin embargo, puede proporcionarles un argumento de retorno. Esto hace que el código sea aún más feo para los constructores. También desactiva la sobrecarga de los operadores, entre otras cosas. Esa es la razón por la que dije que la respuesta corta es ** no **. – utnapistim

+0

Una mejor opción puede ser evitar escribir constructores que pueden fallar: use una función init() separada que en su lugar haga las cosas más pesadas. –

0

Otro enfoque es, en lugar de lanzar una excepción, establecer un indicador de error global, y devolver una entrada legal pero arbitraria. Luego, verificando cada iteración de ciclo, ya sea que el indicador de error global esté o no configurado, y si lo está, devuelve.

Si es lo suficientemente cuidadoso, puede asegurarse de que la devolución de los datos legales nunca causará fallas o un comportamiento indefinido. Por lo tanto, no debería preocuparse de que el software siga funcionando un poco hasta que llegue a la condición de comprobación de errores más cercana.

Por ejemplo

#define WHILE_R(cond,return_value) while (cond) {\ 
    if (exception_thrown) return return_value 
#define ENDWHILE() } 

bool isPolyLegal(Poly p) { 
    PolyIter it(p); 
    WHILE_R(it.next(),true) //return value is arbitary 
    ... 
     if (not_enough_memory) exception_thrown = true; 
    ... 
    ENDWHILE() 
}