Question

Si j'utilise assert() et l'affirmation échoue alors assert() appellera abort(), mettant fin brusquement au programme en cours.Je ne peux pas me permettre cela dans mon code de production.Existe-t-il un moyen d'affirmer au moment de l'exécution tout en étant capable de détecter les assertions ayant échoué afin d'avoir la possibilité de les gérer avec élégance ?

Était-ce utile?

La solution

Oui, en fait, il y en a.Vous devrez écrire vous-même une fonction d'assertion personnalisée, comme en C++ assert() est exactement celui de C assert(), avec le abort() "fonctionnalité" intégrée.Heureusement, c’est étonnamment simple.

Assert.hh

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

La fonction ci-dessus lèvera une exception si un prédicat ne tient pas.Vous aurez alors la possibilité d'attraper l'exception.Si vous n'attrapez pas l'exception, terminate() sera appelé, ce qui mettra fin au programme de la même manière que abort().

Vous vous demandez peut-être pourquoi il ne serait pas possible d'optimiser l'assertion lorsque nous construisons pour la production.Dans ce cas, vous pouvez définir des constantes qui signifieront que vous construisez pour la production, puis vous référer à la constante lorsque vous 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 est une constante alors l'appel à Assert() sera compilé en production, même si l'assertion n'est pas une expression constante.Il y a un léger inconvénient à ce que le fait de se référer à CHECK_WRONG on tape un peu plus.Mais en échange, nous obtenons un avantage dans la mesure où nous pouvons classer différents groupes d'assertions et activer et désactiver chacun d'eux comme bon nous semble.Ainsi, par exemple, nous pourrions définir un groupe d'assertions que nous souhaitons activer même dans le code de production, puis définir un groupe d'assertions que nous souhaitons voir uniquement dans les versions de développement.

Le Assert() la fonction équivaut à taper

if( !assertion ) throw X();

mais cela indique clairement l'intention du programmeur :faire une affirmation.Les assertions sont également plus faciles à saisir avec cette approche, tout comme les assertions simples. assert()s.

Pour plus de détails sur cette technique, voir The C++ Programming Language 3e de Bjarne Stroustrup, section 24.3.7.2.

Autres conseils

fonctions de rapport d'erreurs de glib adoptez l’approche consistant à continuer après une assertion.glib est la bibliothèque sous-jacente indépendante de la plate-forme utilisée par Gnome (via GTK).Voici une macro qui vérifie une précondition et imprime une trace de pile si la précondition échoue.

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

Voici la fonction qui imprime la trace de la pile, écrite pour un environnement qui utilise la chaîne d'outils gnu (gcc) :

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

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

Voici comment utiliser les 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);
}

Les assertions en C/C++ ne s'exécutent que dans les versions de débogage.Cela ne se produira donc pas au moment de l'exécution.En général, les assertions doivent marquer des choses qui, si elles se produisent, indiquent un bug, et généralement afficher des hypothèses dans votre code, etc.

Si vous souhaitez avoir du code qui vérifie les erreurs au moment de l'exécution (dans la version), vous devriez probablement utiliser des exceptions plutôt que des assertions, car c'est pour cela qu'elles sont conçues.Votre réponse enveloppe essentiellement un lanceur d'exceptions dans la syntaxe assert.Bien que cela fonctionne, je ne vois aucun avantage particulier à cela par rapport à la simple levée de l'exception en premier lieu.

Voici ce que j'ai dans "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)

Sur cette base, remplacez l'appel à abort() par un throw( exception ).Et au lieu de printf, vous pouvez formater la chaîne dans le message d'erreur de l'exception.Au final, vous obtenez quelque chose comme ceci :

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

Je n'ai pas essayé de le compiler, mais vous comprenez le sens.

Note:vous devrez vous assurer que l'en-tête "exception" est toujours inclus, ainsi que les boost (si vous décidez de l'utiliser pour formater le message d'erreur).Mais vous pouvez aussi faire de "my_assert" une fonction et déclarer uniquement son prototype.Quelque chose comme:

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

Et implémentez-le quelque part où vous pouvez librement inclure tous les en-têtes dont vous avez besoin.

Enveloppez-le dans du #ifdef DEBUG si vous en avez besoin, ou non si vous souhaitez toujours exécuter ces vérifications.

Si vous souhaitez lancer une chaîne de caractères contenant des informations sur l'assertion :http://xll8.codeplex.com/SourceControl/latest#xll/ensure.h

_set_error_mode(_OUT_TO_MSGBOX);

croyez-moi, cette fonction peut vous aider.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top