Domanda

Se uso assert() e allora l'affermazione fallisce assert() chiamerà abort(), terminando bruscamente il programma in esecuzione.Non posso permettermelo nel mio codice di produzione.Esiste un modo per affermare in runtime e allo stesso tempo essere in grado di individuare le asserzioni non riuscite in modo da avere la possibilità di gestirle con garbo?

È stato utile?

Soluzione

Sì, in effetti c'è.Dovrai scrivere tu stesso una funzione di asserzione personalizzata, come quella di C++ assert() è esattamente C assert(), con il abort() "funzionalità" inclusa.Fortunatamente, questo è sorprendentemente semplice.

Asserto.hh

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

La funzione precedente genererà un'eccezione se un predicato non è valido.Avrai quindi la possibilità di cogliere l'eccezione.Se non cogli l'eccezione, terminate() verrà chiamato, che terminerà il programma in modo simile a abort().

Potresti chiederti che ne dici di ottimizzare l'asserzione quando stiamo costruendo per la produzione.In questo caso, puoi definire costanti che indicheranno che stai creando per la produzione e quindi fare riferimento alla costante quando 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;
}

Se CHECK_WRONG è una costante quindi la chiamata a Assert() verrà compilato durante la produzione, anche se l'asserzione non è un'espressione costante.C'è un leggero svantaggio nel fare riferimento a CHECK_WRONG digitiamo ancora un po'.Ma in cambio otteniamo il vantaggio di poter classificare vari gruppi di asserzioni e abilitarle e disabilitarle come riteniamo opportuno.Quindi, ad esempio, potremmo definire un gruppo di asserzioni che vogliamo abilitare anche nel codice di produzione, e poi definire un gruppo di asserzioni che vogliamo vedere solo nelle build di sviluppo.

IL Assert() la funzione è equivalente alla digitazione

if( !assertion ) throw X();

ma indica chiaramente l'intento del programmatore:fare un'affermazione.Anche le asserzioni sono più facili da ottenere con questo approccio, proprio come le semplici assert()S.

Per maggiori dettagli su questa tecnica vedere The C++ Programming Language 3e di Bjarne Stroustrup, sezione 24.3.7.2.

Altri suggerimenti

le funzioni di segnalazione degli errori di glib adottare l'approccio di continuare dopo un'asserzione.glib è la libreria di indipendenza dalla piattaforma sottostante utilizzata da Gnome (tramite GTK).Ecco una macro che controlla una precondizione e stampa un'analisi dello stack se la precondizione fallisce.

#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)

Ecco la funzione che stampa lo stack trace, scritta per un ambiente che utilizza la gnu toolchain (gcc):

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

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

Ecco come utilizzeresti le macro:

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);
}

Le asserzioni in C/C++ vengono eseguite solo nelle build di debug.Quindi questo non accadrà in fase di esecuzione.In generale le affermazioni dovrebbero contrassegnare cose che, se accadono, indicano un bug e generalmente mostrano ipotesi nel codice, ecc.

Se vuoi avere un codice che controlli gli errori in fase di runtime (nel rilascio) dovresti probabilmente usare le eccezioni piuttosto che le asserzioni poiché questo è ciò per cui sono progettate.La tua risposta fondamentalmente racchiude un lanciatore di eccezioni nella sintassi assert.Anche se funzionerà, non vi è alcun vantaggio particolare che posso vedere rispetto al semplice lancio dell'eccezione in primo luogo.

Ecco cosa ho in "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)

Sulla base di ciò, sostituisci la chiamata ad abort() con un lancio( eccezione ).E invece di printf puoi formattare la stringa nel messaggio di errore dell'eccezione.Alla fine, ottieni qualcosa del genere:

#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))

Non ho provato a compilarlo, ma hai capito il significato.

Nota:dovrai assicurarti che l'intestazione "exception" sia sempre inclusa, così come quella di boost (se decidi di usarla per formattare il messaggio di errore).Ma puoi anche rendere "my_assert" una funzione e dichiararne solo il prototipo.Qualcosa di simile a:

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

E implementalo da qualche parte dove puoi includere liberamente tutte le intestazioni di cui hai bisogno.

Avvolgilo in un po' di #ifdef DEBUG se ne hai bisogno, oppure no se vuoi sempre eseguire quei controlli.

Se vuoi lanciare una stringa di caratteri con informazioni sull'asserzione:http://xll8.codeplex.com/SourceControl/latest#xll/ensure.h

_set_error_mode(_OUT_TO_MSGBOX);

credimi, questa funzione può aiutarti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top