If I can use assertion, how to do it?
There is a standard assert macro provided by #include <cassert>. When
the symbol NDEBUG (think "non-debug") is not defined, assert will
evaluate its argument and if it is false, it will print a brief
diagnostic message and abort the program. When the symbol NDEBUG is
defined, assert does nothing (not even evaluate the argument).
Depending on your development environment, it may offer its own
version of assert, or it may handle asserts differently (for instance,
in a debugger, it might break at the point of a failed assert to give
you a chance to study the situation). You could also create your own
assert mechanism that may have different behavior, as other posters
have discussed.
#include <cassert>
#include <iostream>
void print_name(char const * name)
{
assert(name != NULL);
assert(strlen(name) > 0);
std::cout << name << std::endl;
}
You may find it useful to put some text describing the situation
inside the assert:
assert(name!=NULL && "print_name requires a valid pointer");
This is a trick that relies on the fact that the string literal will
always evaluate to true, so anding it with your test won't change the
logic of your test, but this little description can appear in the
diagnostic message provided when the program aborts.
should I use exception handling and how?
Exceptions can get more complicated. I am not up to a thorough
discussion of them, but I will touch upon them. You can perform
tests, and then throw exceptions, which functions higher up the call
stack may catch and act upon. If no one catches it, the program will
terminate. The above example, with exceptions, might look like:
#include <stdexcept>
#include <iostream>
void print_name(char const * name)
{
if(name == NULL)
throw std::invalid_argument("print_name invalid ptr");
if(strlen(name) < 1)
throw std::length_error("print_name empty name");
std::cout << name << std::endl;
}
A couple things to consider:
- Ease of use:
asserts are simple. You can put in an assert, and it helps you spot
bugs while you run the program. Little development time is spent.
exceptions are more complicated. It's easy to throw an exception, but
that action can cause behavior (via a catch) that might effect the way
the error is seen, and make it harder to track down the origin of the
problem.
This is actually something to think about. Development time is a real
cost in software. Also, if you find one technique a lot of trouble to
use, you may find yourself skipping over programming in such tests
that you will regret later on ("Obviously name isn't going to be
empty, that is just silly, everyone has a name. I am not going to
check for it.").
* Note, the operating system will still clean up some things (like
dynamic memory allocation), but other things that you might want done
(perhaps gracefully closing a network connection) may not happen.
- Flexibility:
asserts offer little flexibility. They are for debug mode only. When
an error is detected, the program is aborted. No destructors are
called, so things might not clean up neatly*.
exceptions offer much flexibility. They will be active in both debug
and release mode. They give a chance for more complicated error
handling. For instance, print_name might throw an exception because
it doesn't have name, but whoever called print_name might be able to
catch it, and add more information to the diagnostic. Destructors are
called so things will clean up more completely.
Another thing you might consider is how serious is the problem. For
instance, say I have an option for the user to get info about the
program, display version number, who it is registered to, etc. If the
code executes, and there is no name, me as a programmer might find it
nice for execution to hault so this unusual state can be brought to my
attention and I can look to see what might be wrong. As an end user,
having a name in an About Box probably isn't a big deal. I certainly
don't want the program to terminate (like an uncaught exception would
do).
In constrast, if I have a function that is about to use a pointer,
dereferencing an invalid pointer could cause the program to crash in
an uncontroled manner, so I probably don't want the user to ever
experience this. In this scenario, an exception might be better.
So here is an example that uses both techniques:
void print_name(char const * name)
{
if(name == NULL)
throw std::invalid_argument("print_name invalid ptr");
assert(strlen(name)>0 && "print_name empty name");
}
Finally, your development environment might also push you one way or
another. Some debuggers will make it more useful to use asserts to
debug, whilst some will make it easier to use exceptions. Play around
and see which one you find more intuitive.