views:

1610

answers:

4

If I use assert() and the assertion fails then assert() will call abort(), ending the running program abruptly. I can't afford that in my production code. Is there a way to assert in runtime yet be able to catch failed assertions so I have the chance to handle them gracefully?

+6  A: 

Yes, as a matter of fact there is. You will need to write a custom assert function yourself, as C++'s assert() is exactly C's assert(), with the abort() "feature" bundled in. Fortunately, this is surprisingly straightforward.

Assert.hh

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

The above function will throw an exception if a predicate doesn't hold. You will then have the chance to catch the exception. If you don't catch the exception, terminate() will be called, which will end the program similarly to abort().

You may wonder what about optimizing away the assertion when we're building for production. In this case, you can define constants that will signify that you're building for production and then refer to the constant when you 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;
}

If CHECK_WRONG is a constant then the call to Assert() will be compiled away in production, even if the assertion is not a constant expression. There is a slight disadvantage in that by referring to CHECK_WRONG we type a little more. But in exchange we gain an advantage in that we can classify various groups of assertions and enable and disable each of them as we see fit. So, for example we could define a group of assertions that we want enabled even in production code, and then define a group of assertions that we only want to see in development builds.

The Assert() function is equivalent to typing

if( !assertion ) throw X();

but it clearly indicates the intent of the programmer: make an assertion. Assertions are also easier to grep for with this approach, just like plain assert()s.

For more details on this technique see Bjarne Stroustrup's The C++ Programming Language 3e, section 24.3.7.2.

wilhelmtell
Since you can only ever get default constructed exceptions, there's not much benefit to this since you can't transport any run time data to the catch site...
Greg Rogers
What is the added value of templatizing the assert argument rather than making it int? It's going to have to be converted to int eventually anyway.
JohnMcG
@Greg RogersBjarne Stroustrup offers a solution to the problem you mention: you can add a customized object you want to be thrown as an extra parameter of Assert(). The downside is that Assert() won't look like assert() anymore because of the extra parameter.
wilhelmtell
@JohnMcG: the template parameter gives me extra flexibility. you can pass anything to the Assert() function for which operator!() is defined and returns something convertible to a bool. booleans work, ints work and so do some custom objects.
wilhelmtell
it's very simple, the code suggests this is nothing but a wrapped exception. but i hoped people would try to look beyond the obvious. similar rants can said the old assert(), yet it serves a very useful task. this trick merely tries to solve a shortcoming of the old assert() with no extra cost.
wilhelmtell
apart from the exception thrown, this serves more as a concept than other code we're used to see.
wilhelmtell
+4  A: 

Asserts in C/C++ only run in debug builds. So this won't happen at runtime. In general asserts should mark things that if they happen indicate a bug, and generally show assumptions in your code etc.

If you want to have code that checks for errors at runtime (in release) you should probably use exceptions rather than asserts as these are what they are designed to do. Your answer basically wraps an exception thrower in assert syntax. While this will work, there is no particular advantage to this that I can see over just throwing the exception in the first place.

Ben Childs
Asserts running or not running in production builds is a compiler option. They most definitely run in gcc by default regardless of optimization options.
Dark Shikari
I personally find it a good idea to keep the asserts in release mode. You may prefer that to random crashes when your client uses the app...
Alexandre C.
A: 

glib's error reporting functions take the approach of continuing after an assert. glib is the underlying platform independence library that Gnome (via GTK) uses. Here's a macro that checks a precondition and prints a stack trace if the precondition fails.

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

Here's the function that prints the stack trace, written for an environment that uses the 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);
}

This is how you'd use the 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);
}
indiv
+1  A: 

Here's what I have my 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)

Based on that, replace the call to abort() by a throw( exception ). And instead of printf you can format the string into the exception's error message. In the end, you get something like this:

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

I haven't tried to compile it, but you get the meaning.

Note: you'll need to make sure that the "exception" header is always included, as well as boost's (if you decide to use it for formatting the error message). But you can also make "my_assert" a function and only declare its prototype. Something like:

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

And implement it somewhere where you can freely include all the headers you require.

Wrap it in some #ifdef DEBUG if you need it, or not if you always want to run those checks.

rlerallut
So it remains to argue whether a macro is better than a template function.Also, the sequence operator means operands may be performed in any arbitrary order. I doubt this is what you mean in __asert().
wilhelmtell
The "__assert" function in the first block comes from my system header (0S 10.4) I hope they know what they are doing. The macro makes it easier to get FILE and LINE information. But at some point it gets quite cosmetic...
rlerallut