Boost logo

Boost :

From: John Torjo (john_at_[hidden])
Date: 2003-05-21 07:55:55


Hi all,

smart_assert is ready for a test drive (only first and second gear, please
;-))

A lot more to do, but looks pretty cool so far.
Compiles and runs with VC6 and gcc 3.2.

I've attached it. You can also get it from
www.torjo.com/smart_assert.zip

There is no formal documentation yet, and it has a few examples (I think
they're pretty
straightforward).
Please let me know what you think.

Summary:
The purpose of a SMART_ASSERT is to show as much information as possible, in
case
it fails.
Its usage is easy and straighforward.
All failed assertions are logged.
It features multiple assertion levels (not all asserts are the same ;-)),
it can work in release mode (SMART_VERIFY).
A lot of things can be set at run-time allowing for great flexibility
(for instance, you can set the handler
for a debug assertion to throw an exception, if you wish).
It will also feature ENFORCE (not fully implemented yet).

1. Syntax:
SMART_ASSERT( expression)(variable_involved_in_expr_1)(variable_involved_in_
expr_2)...;

Example:
SMART_ASSERT( (i < j) || (i < 0) || (k == -1) ) (i)(j)(k);

After the expression, you'll specify all the values that were involved in
it,
surrounded by (), like this: '(i)(j)(k)'

2. What happens when an assertion fails.
First, an assert_context builds up, containing:
- the context in which the ASSERTion appeared (__FILE__, __LINE__, etc.) -
customizable
- the expression, as a string (example: "(i > 1) || (j < 0) || (k != i)")
- the values involved in the expression (example: "i" and "3" (i's value),
    "j" and "2" (j's value), "k" and "1" (k's value)
- the level of the ASSERTion and an optional message (see below)

Then:
- the ASSERTion is logged. All ASSERTions are logged, no matter what. This
way,
  even after the application crashes, you can still look at the failed
assertions (if they
  were logged to an external file).
- based on the ASSERTion's level (see below) the ASSERTion is handled.

Both the logger and the handler receive a 'const assert_context&' reference,
and are able
to see where the error occured, etc.
Both the logger and handler can be customized (you can set your own).

3. Multiple levels of ASSERTion
ASSERTions can have different levels, depending on the gravity of the
situation
(how bad would it be for an ASSERTion to fail? would it, for instance, be
critical?)

So far, I've defined four levels, but you can add your own:
- warn (warning)
- debug (debug assertion, DEFAULT)
- error (this is an error)
- critical (this is critical, the app. probably became unstable)

For each level of ASSERTion, you can set your own handler at RUN-TIME
(not necessary the same for all).
The default handlers are as follows:
- warn: a summary of the assertion is printed on the console,
  and execution continues
- debug: a summary of the assertion is printed, and asks the user
  what to do (Ignore/ Retry/ Abort/etc.)
- error: a std::runtime_error is thrown
- critical: all details of the assertion are printed on the console,
  and abort()s.

4. Logging of the ASSERTions
Same as for handlers, you can set your own logging class.
However, this is the same, no matter the type of assertion (unlike
handlers).

Default logger dumps all details of the assertion to
a file called './asserts.txt'.

5. SMART_ASSERT_CONTEXT
This is the context in which the assertion appeared, relative to the
application.
So far, it contains the __FILE__ and __LINE__ (and __FUNCTION__, if
present).
You can customize it (just define it prior to #including smart_assert.hpp).

6. Setting ASSERTion levels, msg.
The default ASSERTion level is debug. You can easily change it, like this:

SMART_ASSERT( i < 1000)(i); // default level;

can be changed into:

SMART_ASSERT( i < 1000)(i).level( lvl_error, "too many users!"); // custom
level OR
SMART_ASSERT( i < 1000)(i).error( "too many users!");
SMART_ASSERT( i < 1000)(i).level( lvl_error); // no user-friendly message
SMART_ASSERT( i < 1000)(i).error(); // no user-friendly message

So, setting the level is as easy as calling the level() function.
You can also provide an extra parameter - a ***user-friendly message***
(most likely to be shown to the user ;-)).

In case you don't want to change the level, but want to set the
user-friendly message,
use the .msg() function:
SMART_ASSERT( i < 1000)(i).msg( "too many users!");

7. Handling simple ASSERTions
If assertion is "trivial" (it contains only one argument and no operators),
you don't need to specify it again.
That is:
SMART_ASSERT(p)(p);
is equivalent to
SMART_ASSERT(p);

8. Using the v_ macro
When you have a lengthy function, in case the assertion fails,
it might be called twice.

Example:
SMART_ASSERT( lengthy_function() > 10) (lengthy_function());

However, you'll probably not want that. You want, in this case,
the lengthy_function() to be called ONLY ONCE.
Use the v_ macro for this:
SMART_ASSERT( v_(lengthy_function()) > 10);

9. Custom printing (NOT TESTED YET).
You can set your own printer class.
The default printer, just prints values using '<<'.
However, for your custom classes, maybe that's not enough
(for instance, for a pointer, if it's non-null, you might want
to print its contents). You can customize that.

10. SMART_VERIFY
You should understand that SMART_ASSERT works ONLY IN DEBUG mode.
When you're in release mode, the SMART_ASSERTs are removed (much like
the regular 'assert's).
So, an ASSERT like this: won't throw an error in release mode:

int *p = 0;
SMART_ASSERT(p).error("null pointer!");

In case you want something that will work in both debug and release,
use 'SMART_VERIFY'.

SMART_VERIFY behaves ***exacly like*** SMART_ASSERT, with two differences:
- SMART_VERIFY will work in release mode as well
- the default level of SMART_VERIFY is 'error'.
  Which means that if a SMART_VERIFY fails, an exception will be thrown
  by default. This makes sense, since the assertions you use in release
  could most likely be fatal.

You should note that, even for a SMART_VERIFY, you can change its level
(as said, you can do anything you do with a SMART_ASSERT):

// in case we have the default handler, this will show the assertion,
// and ask the user what to do.
int *p = 0;
SMART_VERIFY(p).debug( "we'll ask the user what to do");

11. Changing the "debug" mode.
As said above, in release, all SMART_ASSERTs are removed.
But in some cases (like, pre-alfa, alfa, test releases) you'll still
want ASSERTions, even if you ship release versions.
By default,
- in debug mode, SMART_ASSERTs exist
- in release mode, SMART_ASSERTs are removed.

In case you want to override this, just do one of these:

#define BOOST_SMART_ASSERT_MODE 0 // behave as release
#define BOOST_SMART_ASSERT_MODE 1 // behave as debug

That's about it so far.

Best,
John

--
John Torjo
-- "Practical C++" column writer for builder.com.com
Freelancer, C++ consultant
mailto:john_at_[hidden]



Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk