C+ EXCEPTIONS

PREVIOUS

EXCEPTION HANDLING

Q: What happens when a function throws an exception that was not specified by an exception
specification for this function?
A: Unexpected() is called, which, by default, will eventually trigger abort().

Q: What does throw; (without an exception object after the throw keyword) mean? Where would
I use it?
A: You might see code that looks something like this:
class MyException {
public:
...
void addInfo(const std::string& info);
...
};
void f()
{
try {
...
}
catch (MyException& e) {
e.addInfo("f() failed");
throw;
}
}
In this example, the statement throw; means "re-throw the current exception." Here, a function
caught an exception (by non-const reference), modified the exception (by adding information to
it), and then re-threw the exception. This idiom can be used to implement a simple form of stacktrace, by adding appropriate catch clauses in the important functions of your program.
Another re-throwing idiom is the "exception dispatcher":
void handleException()
{
try {
throw;
}
catch (MyException& e) {
...code to handle MyException...
}
catch (YourException& e) {
...code to handle YourException...
}
}
void f()
{
try {
...something that might throw...
}
catch (...) {
handleException();
}
}
This idiom allows a single function (handleException()) to be re-used to handle exceptions in a
number of other functions.

Q: How do I throw polymorphically? A: Sometimes people write code like:
class MyExceptionBase { };
class MyExceptionDerived : public MyExceptionBase { };
void f(MyExceptionBase& e)
{
// ...
throw e;
}
void g()
{
MyExceptionDerived e;
try {
f(e);
}
catch (MyExceptionDerived& e) {
...code to handle MyExceptionDerived...
}
catch (...) {
...code to handle other exceptions...
}
}
If you try this, you might be surprised at run-time when your catch (...) clause is entered, and not
your catch (MyExceptionDerived&) clause. This happens because you didn't throw
polymorphically. In function f(), the statement throw e; throws an object with the same type as
the static type of the expression e. In other words, it throws an instance of MyExceptionBase.
The throw statement behaves as-if the thrown object is copied, as opposed to making a "virtual
copy".
Fortunately it's relatively easy to correct:
class MyExceptionBase {
public:
virtual void raise();
};
void MyExceptionBase::raise()
{ throw *this; }
class MyExceptionDerived : public MyExceptionBase {
public:
virtual void raise();
};
void MyExceptionDerived::raise()
{ throw *this; }
void f(MyExceptionBase& e)
{
// ...
e.raise();
}
void g()
{
MyExceptionDerived e;
try {
f(e);
}
catch (MyExceptionDerived& e) {
...code to handle MyExceptionDerived...
}
catch (...) {
...code to handle other exceptions...
}
}
Note that the throw statement has been moved into a virtual function. The statement e.raise() will
exhibit polymorphic behavior, since raise() is declared virtual and e was passed by reference. As
before, the thrown object will be of the static type of the argument in the throw statement, but
within MyExceptionDerived::raise(), that static type is MyExceptionDerived, not
MyExceptionBase.

Q: When I throw this object, how many times will it be copied? A: Depends. Might be "zero."
Objects that are thrown must have a publicly accessible copy-constructor. The compiler is
allowed to generate code that copies the thrown object any number of times, including zero.
However even if the compiler never actually copies the thrown object, it must make sure the
exception class's copy constructor exists and is accessible.

Q: But MFC seems to encourage the use of catch-by-pointer; should I do the same?A: Depends. If you're using MFC and catching one of their exceptions, by all means, do it their
way. Same goes for any framework: when in Rome, do as the Romans. Don't try to force a
framework into your way of thinking, even if "your" way of thinking is "better." If you decide to
use a framework, embrace its way of thinking use the idioms that its authors expected you to use.
But if you're creating your own framework and/or a piece of the system that does not directly
depend on MFC, then don't catch by pointer just because MFC does it that way. When you're not
in Rome, you don't necessarily do as the Romans. In this case, you should not. Libraries like
MFC predated the standardization of exception handling in the C++ language, and some of these
libraries use a backwards-compatible form of exception handling that requires (or at least
encourages) you to catch by pointer.
The problem with catching by pointer is that it's not clear who (if anyone) is responsible for
deleting the pointed-to object. For example, consider the following:
MyException x;
void f()
{
MyException y;
try {
switch (rand() % 3) {
case 0: throw new MyException;
case 1: throw &x;
case 2: throw &y;
}
}
catch (MyException* p) {
...  should we delete p here or not???!?
}
}
There are three basic problems here:
It might be tough to decide whether to delete p within the catch clause. For example, if object x
is inaccessible to the scope of the catch clause, such as when it's buried in the private part of
some class or is static within some other compilation unit, it might be tough to figure out what to
do.
If you solve the first problem by consistently using new in the throw (and therefore consistently
using delete in the catch), then exceptions always use the heap which can cause problems when
the exception was thrown because the system was running low on memory.
If you solve the first problem by consistently not using new in the throw (and therefore
consistently not using delete in the catch), then you probably won't be able to allocate your
exception objects as locals (since then they might get destructed too early), in which case you'll
have to worry about thread-safety, locks, semaphores, etc. (static objects are not intrinsically
thread-safe).
This isn't to say it's not possible to work through these issues. The point is simply this: if you
catch by reference rather than by pointer, life is easier. Why make life hard when you don't have
to?
The moral: avoid throwing pointer expressions, and avoid catching by pointer, unless you're
using an existing library that "wants" you to do so.

Q: What are some ways try / catch / throw can improve software quality?A: By eliminating one of the reasons for if statements.
The commonly used alternative to try / catch / throw is to return a return code (sometimes called
an error code) that the caller explicitly tests via some conditional statement such as if. For
example, printf(), scanf() and malloc() work this way: the caller is supposed to test the return
value to see if the function succeeded.
Although the return code technique is sometimes the most appropriate error handling technique,
there are some nasty side effects to adding unnecessary if statements:
Degrade quality: It is well known that conditional statements are approximately ten times more
likely to contain errors than any other kind of statement. So all other things being equal, if you
can eliminate conditionals / conditional statements from your code, you will likely have more
robust code.
Slow down time-to-market: Since conditional statements are branch points which are related to
the number of test cases that are needed for white-box testing, unnecessary conditional
statements increase the amount of time that needs to be devoted to testing. Basically if you don't
exercise every branch point, there will be instructions in your code that will never have been
executed under test conditions until they are seen by your users/customers. That's bad.
Increase development cost: Bug finding, bug fixing, and testing are all increased by unnecessary
control flow complexity.
So compared to error reporting via return-codes and if, using try / catch / throw is likely to result
in code that has fewer bugs, is less expensive to develop, and has faster time-to-market. Of
course if your organization doesn't have any experiential knowledge of try / catch / throw, you
might want to use it on a toy project first just to make sure you know what you're doing  you
should always get used to a weapon on the firing range before you bring it to the front lines of a
shooting war.
Q: What should I throw?
A: C++, unlike just about every other language with exceptions, is very accomodating when it
comes to what you can throw. In fact, you can throw anything you like. That begs the question
then, what should you throw?
Generally, it's best to throw objects, not built-ins. If possible, you should throw instances of
classes that derive (ultimately) from the std::exception class. By making your exception class
inherit (ultimately) from the standard exception base-class, you are making life easier for your
users (they have the option of catching most things via std::exception), plus you are probably
providing them with more information (such as the fact that your particular exception might be a
refinement of std::runtime_error or whatever).
The most common practice is to throw a temporary:
#include
class MyException : public std::runtime_error {
public:
MyException() : std::runtime_error("MyException") { }
};
void f()
{
// ...
throw MyException();
}
Here, a temporary of type MyException is created and thrown. Class MyException inherits from
class std::runtime_error which (ultimately) inherits from class std::exception.

Q: What should I catch?A: In keeping with the C++ tradition of "there's more than one way to do that" (translation: "give
programmers options and tradeoffs so they can decide what's best for them in their situation"),
C++ allows you a variety of options for catching.
You can catch by value.
You can catch by reference.
You can catch by pointer.
In fact, you have all the flexibility that you have in declaring function parameters, and the rules
for whether a particular exception matches (i.e., will be caught by) a particular catch clause are
almost exactly the same as the rules for parameter compatibility when calling a function.
Given all this flexibility, how do you decide what to catch? Simple: unless there's a good reason
not to, catch by reference. Avoid catching by value, since that causes a copy to be made and the
copy can have different behavior from what was thrown. Only under very special circumstances
should you catch by pointer.

PREVIOUS

No comments:

Post a Comment