2008-09-01 17 views
23

Si uso assert() y la aserción falla, entonces assert() llamará al abort(), terminando el programa en ejecución abruptamente. No puedo pagar eso en mi código de producción. ¿Hay alguna manera de afirmar en tiempo de ejecución pero poder capturar las afirmaciones fallidas para que tenga la oportunidad de manejarlas correctamente?¿Cómo puedo assert() sin usar abort()?

Respuesta

23

Sí, de hecho lo hay. Tendrá que escribir una función de afirmación personalizada usted mismo, ya que C++ 's assert() es exactamente C assert(), con la abort() "función" incluida. Afortunadamente, esto es sorprendentemente sencillo.

Assert.hh

template <typename X, typename A> 
inline void Assert(A assertion) 
{ 
    if(!assertion) throw X(); 
} 

La función anterior será una excepción si un predicado no se sostiene. Entonces tendrás la oportunidad de atrapar la excepción. Si no detecta la excepción, se llamará al terminate(), que finalizará el programa de manera similar al abort().

Quizás se pregunte qué hay acerca de cómo optimizar la afirmación cuando estamos creando para la producción. En este caso, puede definir constantes que significarán que está compilando para producción y luego consultar la constante cuando Assert().

debug.hh

#ifdef NDEBUG 
    const bool CHECK_WRONG = false; 
#else 
    const bool CHECK_WRONG = true; 
#endif 

main.cc

#include<iostream> 

struct Wrong { }; 

int main() 
{ 
    try { 
     Assert<Wrong>(!CHECK_WRONG || 2 + 2 == 5); 
     std::cout << "I can go to sleep now.\n"; 
    } 
    catch(Wrong e) { 
     std::cerr << "Someone is wrong on the internet!\n"; 
    } 

    return 0; 
} 

Si CHECK_WRONG es una constante, entonces la llamada a Assert() será compilado de distancia en la producción, incluso si la afirmación es no es una expresión constante. Hay una ligera desventaja en que al hacer referencia a CHECK_WRONG tecleamos un poco más. Pero a cambio obtenemos una ventaja ya que podemos clasificar varios grupos de aserciones y habilitar y deshabilitar cada una de ellas según lo consideremos oportuno. Entonces, por ejemplo, podríamos definir un grupo de aserciones que queremos habilitar incluso en el código de producción, y luego definir un grupo de aserciones que solo queremos ver en las compilaciones de desarrollo.

La función Assert() es lo mismo que escribir

if(!assertion) throw X(); 

pero indica claramente la intención del programador: hacer una afirmación. Las afirmaciones también son más fáciles de grep con este enfoque, al igual que assert() s llano.

Para más detalles sobre esta técnica, consulte El lenguaje de programación C++ de Bjarne Stroustrup, sección 24.3.7.2.

+0

Ya que se puede solamente consigue nunca por defecto excepciones construidos, no hay mucho beneficio para esto, ya que no se puede transportar datos en tiempo de ejecución a el sitio de captura ... –

+0

¿Cuál es el valor agregado de templatar el argumento afirmar en lugar de hacerlo int? En cualquier caso, tendrá que convertirse a int. – JohnMcG

+0

@Greg Rogers Bjarne Stroustrup ofrece una solución al problema que menciona: puede agregar un objeto personalizado que desee que se arroje como un parámetro adicional de Assert(). La desventaja es que Assert() ya no se verá como assert() debido al parámetro extra. – wilhelmtell

6

Las afirmaciones en C/C++ solo se ejecutan en compilaciones de depuración. Entonces esto no sucederá en tiempo de ejecución. En general, las afirmaciones deben marcar cosas que, si suceden, indican un error, y generalmente muestran suposiciones en su código, etc.

Si desea tener un código que verifique errores en el tiempo de ejecución (en versión) probablemente debería usar excepciones en lugar de afirma que son para lo que están diseñados. Su respuesta básicamente envuelve a un lanzador de excepción en la sintaxis de afirmación. Si bien esto funcionará, no hay una ventaja especial en esto que pueda ver más que lanzar la excepción en primer lugar.

+0

Afirma que la ejecución o no ejecución en compilaciones de producción es una opción del compilador. Definitivamente se ejecutan en gcc de forma predeterminada independientemente de las opciones de optimización. –

+2

Personalmente, me parece una buena idea mantener las afirmaciones en modo de lanzamiento. Puede preferir eso a bloqueos aleatorios cuando su cliente usa la aplicación ... –

+2

Especialmente si su programa dará lugar a un comportamiento indefinido si esa afirmación se vuelve falsa; le permitirá al cliente al menos dar un mensaje de error más valioso y pasos de reproducción más fáciles; teniendo en cuenta que NUNCA debía suceder en primer lugar; y pensaste (y garantizaste) que no sería así. – dcousens

10

glib's error reporting functions adopta el enfoque de continuar después de una afirmación. glib es la biblioteca de independencia de la plataforma subyacente que utiliza Gnome (a través de GTK). Aquí hay una macro que verifica una precondición e imprime un seguimiento de pila si falla la condición previa.

#define RETURN_IF_FAIL(expr)  do {     \ 
if (!(expr))           \ 
{              \ 
     fprintf(stderr,        \ 
       "file %s: line %d (%s): precondition `%s' failed.", \ 
       __FILE__,           \ 
       __LINE__,           \ 
       __PRETTY_FUNCTION__,        \ 
       #expr);            \ 
     print_stack_trace(2);          \ 
     return;             \ 
};    } while(0) 
#define RETURN_VAL_IF_FAIL(expr, val) do {       \ 
if (!(expr))              \ 
{                 \ 
     fprintf(stderr,            \ 
       "file %s: line %d (%s): precondition `%s' failed.",  \ 
       __FILE__,            \ 
       __LINE__,            \ 
       __PRETTY_FUNCTION__,         \ 
       #expr);             \ 
     print_stack_trace(2);           \ 
     return val;             \ 
};    } while(0) 

Aquí está la función que imprime el seguimiento de la pila, escrito para un entorno que utiliza la cadena de herramientas GNU (GCC):

void print_stack_trace(int fd) 
{ 
    void *array[256]; 
    size_t size; 

    size = backtrace (array, 256); 
    backtrace_symbols_fd(array, size, fd); 
} 

Ésta es la forma en que tendría que utilizar las macros:

char *doSomething(char *ptr) 
{ 
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL); // same as assert(ptr != NULL), but returns NULL if it fails. 

    if(ptr != NULL)  // Necessary if you want to define the macro only for debug builds 
    { 
     ... 
    } 

    return ptr; 
} 

void doSomethingElse(char *ptr) 
{ 
    RETURN_IF_FAIL(ptr != NULL); 
} 
+3

@wilhelmtell I Creo que esta es la mejor solución hasta el momento porque estoy en desacuerdo con el uso de la excepción. La excepción debe ser algo excepcional, sin embargo, el manejo de Assert no lo es. Además, si planea dejar Asserts On en la compilación de lanzamiento, que creo que debería hacer a menos que el rendimiento sea vital, este método será más eficiente. También puede personalizarlo para que su programa no salga necesariamente cuando sucede y obtendrá la información de vuelta. Sugiero leer el capítulo del libro Programador Pragmático sobre Afirmaciones y Excepciones. Además [este enlace] (http://www.gotw.ca/publications/mill22.htm). – ForceMagic

5

Esto es lo que tengo a mi en "assert.h" (Mac OS 10.4):

#define assert(e) ((void) ((e) ? 0 : __assert (#e, __FILE__, __LINE__))) 
#define __assert(e, file, line) ((void)printf ("%s:%u: failed assertion `%s'\n", file, line, e), abort(), 0) 

En función de eso, reemplace la llamada a abort() por un lanzamiento (excepción). Y en lugar de printf puede formatear la cadena en el mensaje de error de la excepción. Al final, se obtiene algo como esto:

#define assert(e) ((void) ((e) ? 0 : my_assert (#e, __FILE__, __LINE__))) 
#define my_assert(e, file, line) (throw std::runtime_error(\ 
    std::string(file:)+boost::lexical_cast<std::string>(line)+": failed assertion "+e)) 

no he tratado de compilar, pero se obtiene el significado.

Nota: deberá asegurarse de que el encabezado de "excepción" siempre esté incluido, así como de los refuerzos (si decide usarlo para formatear el mensaje de error). Pero también puedes hacer que "my_assert" sea una función y solo declarar su prototipo. Algo así como:

void my_assert(const char* e, const char* file, int line); 

Y ejecútelo en algún lugar donde puede incluir libremente todos los encabezados que necesita.

Ajústalo en algún DEPURACIÓN #ifdef si lo necesitas, o no si siempre deseas ejecutar esos controles.

+0

Por lo tanto, queda por discutir si una macro es mejor que una función de plantilla. Además, el operador de secuencia significa que los operandos pueden realizarse en cualquier orden arbitraria. Dudo que esto es lo que quieres decir en __asert(). – wilhelmtell

+0

La función "__assert" en el primer bloque proviene de mi encabezado de sistema (0S 10.4) Espero que sepan lo que están haciendo. La macro hace que sea más fácil obtener información de ARCHIVO y LÍNEA. Pero en algún momento se vuelve bastante estético ... – rlerallut

-1
_set_error_mode(_OUT_TO_MSGBOX); 

créeme, esta función puede ayudar.

+0

Esto parece ser una función solo de Windows: [link] (https://msdn.microsoft.com/en-us/library/aa235388 (v = vs.60). aspx) –

+0

Y como mínimo deberías haber explicado qué encabezado/compilador/lo que se necesita para usarlo. –