Frage

Wenn ich es verwende assert() und die Behauptung schlägt dann fehl assert() werde anrufen abort(), wodurch das laufende Programm abrupt beendet wird.Das kann ich mir in meinem Produktionscode nicht leisten.Gibt es eine Möglichkeit, Behauptungen zur Laufzeit zu erstellen und dennoch fehlgeschlagene Behauptungen abzufangen, sodass ich die Möglichkeit habe, sie ordnungsgemäß zu verarbeiten?

War es hilfreich?

Lösung

Ja, das gibt es tatsächlich.Sie müssen selbst eine benutzerdefinierte Assert-Funktion schreiben, beispielsweise in C++ assert() ist genau C's assert(), mit dem abort() „Feature“ gebündelt.Glücklicherweise ist dies überraschend einfach.

Behaupten.hh

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

Die obige Funktion löst eine Ausnahme aus, wenn ein Prädikat nicht gilt.Sie haben dann die Möglichkeit, die Ausnahme abzufangen.Wenn Sie die Ausnahme nicht abfangen, terminate() aufgerufen, wodurch das Programm auf ähnliche Weise beendet wird abort().

Sie fragen sich vielleicht, wie es wäre, die Behauptung wegzuoptimieren, wenn wir für die Produktion bauen.In diesem Fall können Sie Konstanten definieren, die anzeigen, dass Sie für die Produktion bauen, und dann auf die Konstante verweisen, wenn Sie dies tun 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;
}

Wenn CHECK_WRONG ist eine Konstante, dann der Aufruf von Assert() wird in der Produktion kompiliert, auch wenn die Behauptung kein konstanter Ausdruck ist.Darin liegt ein kleiner Nachteil, wenn man sich darauf bezieht CHECK_WRONG wir tippen noch ein bisschen.Aber im Gegenzug haben wir den Vorteil, dass wir verschiedene Gruppen von Behauptungen klassifizieren und jede davon nach eigenem Ermessen aktivieren und deaktivieren können.So könnten wir beispielsweise eine Gruppe von Behauptungen definieren, die auch im Produktionscode aktiviert werden sollen, und dann eine Gruppe von Behauptungen definieren, die wir nur in Entwicklungs-Builds sehen möchten.

Der Assert() Die Funktion entspricht der Eingabe

if( !assertion ) throw X();

aber es zeigt deutlich die Absicht des Programmierers:eine Behauptung aufstellen.Auch Assertions sind mit diesem Ansatz einfacher zu erfassen, genau wie Plain assert()S.

Weitere Einzelheiten zu dieser Technik finden Sie in Bjarne Stroustrups The C++ Programming Language 3e, Abschnitt 24.3.7.2.

Andere Tipps

Fehlerberichtsfunktionen von glib Nehmen Sie den Ansatz, nach einer Behauptung fortzufahren.glib ist die zugrunde liegende Plattformunabhängigkeitsbibliothek, die Gnome (über GTK) verwendet.Hier ist ein Makro, das eine Vorbedingung prüft und einen Stack-Trace ausgibt, wenn die Vorbedingung fehlschlägt.

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

Hier ist die Funktion, die den Stack-Trace druckt, geschrieben für eine Umgebung, die die GNU-Toolchain (gcc) verwendet:

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

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

So würden Sie die Makros verwenden:

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

Asserts in C/C++ werden nur in Debug-Builds ausgeführt.Das wird also zur Laufzeit nicht passieren.Im Allgemeinen sollten Behauptungen Dinge markieren, die, wenn sie auftreten, auf einen Fehler hinweisen und im Allgemeinen Annahmen in Ihrem Code usw. anzeigen.

Wenn Sie Code haben möchten, der zur Laufzeit (in der Version) nach Fehlern sucht, sollten Sie wahrscheinlich Ausnahmen anstelle von Asserts verwenden, da sie dafür konzipiert sind.Ihre Antwort schließt im Grunde einen Ausnahmeauslöser in die Assert-Syntax ein.Obwohl dies funktionieren wird, sehe ich darin keinen besonderen Vorteil gegenüber dem bloßen Auslösen der Ausnahme.

Folgendes habe ich 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)

Ersetzen Sie auf dieser Grundlage den Aufruf von abort() durch eine throw(-Ausnahme).Und anstelle von printf können Sie die Zeichenfolge in die Fehlermeldung der Ausnahme formatieren.Am Ende erhalten Sie so etwas:

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

Ich habe nicht versucht, es zu kompilieren, aber Sie verstehen, was es bedeutet.

Notiz:Sie müssen sicherstellen, dass der „Exception“-Header immer enthalten ist, ebenso wie Boosts (falls Sie ihn zum Formatieren der Fehlermeldung verwenden möchten).Sie können „my_assert“ aber auch zu einer Funktion machen und nur deren Prototyp deklarieren.Etwas wie:

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

Und implementieren Sie es an einem Ort, an dem Sie alle benötigten Header frei einfügen können.

Wickeln Sie es in etwas #ifdef DEBUG ein, wenn Sie es brauchen, oder nicht, wenn Sie diese Prüfungen immer durchführen möchten.

Wenn Sie eine Zeichenfolge mit Informationen zur Behauptung ausgeben möchten:http://xll8.codeplex.com/SourceControl/latest#xll/ensure.h

_set_error_mode(_OUT_TO_MSGBOX);

Glauben Sie mir, diese Funktion kann Ihnen helfen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top