Pregunta

si uso assert() y la afirmación falla entonces assert() llamará abort(), finalizando abruptamente el programa en ejecución.No puedo permitirme eso en mi código de producción.¿Hay alguna manera de afirmar en tiempo de ejecución y al mismo tiempo poder detectar afirmaciones fallidas para tener la oportunidad de manejarlas con elegancia?

¿Fue útil?

Solución

Sí, de hecho lo hay.Necesitará escribir una función de afirmación personalizada usted mismo, como en C++. assert() es exactamente C assert(), con el abort() "función" incluida.Afortunadamente, esto es sorprendentemente sencillo.

Afirmar.hh

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

La función anterior generará una excepción si un predicado no se cumple.Entonces tendrás la oportunidad de detectar la excepción.Si no captas la excepción, terminate() será llamado, lo que finalizará el programa de manera similar a abort().

Quizás se pregunte qué pasa con la optimización de la afirmación cuando estamos construyendo para producción.En este caso, puede definir constantes que indicarán que está construyendo para producción y luego hacer referencia a la constante cuando Assert().

depurar.hh

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

principal.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() se compilará en producción, incluso si la afirmación no es una expresión constante.Existe una ligera desventaja en el sentido de que al referirse a CHECK_WRONG escribimos un poco más.Pero a cambio obtenemos la ventaja de que podemos clasificar varios grupos de afirmaciones y habilitar y deshabilitar cada una de ellas como mejor nos parezca.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.

El Assert() la función es equivalente a 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 captar con este enfoque, al igual que las simples assert()s.

Para obtener más detalles sobre esta técnica, consulte The C++ Programming Language 3e de Bjarne Stroustrup, sección 24.3.7.2.

Otros consejos

Funciones de informe de errores de Glib adopte el enfoque de continuar después de una afirmación.glib es la biblioteca de independencia de plataforma subyacente que utiliza Gnome (a través de GTK).Aquí hay una macro que verifica una condición previa e imprime un seguimiento de la pila si la condición previa falla.

#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, escrita 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);
}

Así es como usarías 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);
}

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, en general, muestran suposiciones en su código, etc.

Si desea tener un código que verifique errores en tiempo de ejecución (en el lanzamiento), probablemente debería usar excepciones en lugar de afirmaciones, ya que para eso están diseñadas.Su respuesta básicamente envuelve un lanzador de excepciones en sintaxis de afirmación.Si bien esto funcionará, no veo ninguna ventaja particular en esto sobre simplemente lanzar la excepción en primer lugar.

Esto es lo que tengo 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 base a eso, reemplace la llamada a abort() por un throw(excepción).Y en lugar de printf, puede formatear la cadena en el mensaje de error de la excepción.Al final, obtienes 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 intentado compilarlo, pero entiendes el significado.

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

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

E impleméntelo en algún lugar donde pueda incluir libremente todos los encabezados que necesite.

Envuélvalo en #ifdef DEBUG si lo necesita, o no si siempre desea ejecutar esas comprobaciones.

Si desea arrojar una cadena de caracteres con información sobre la aserción:http://xll8.codeplex.com/SourceControl/latest#xll/ensure.h

_set_error_mode(_OUT_TO_MSGBOX);

Créeme, esta función puede ayudarte.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top