FAQs in section [17]:
[17.1] What are some ways try / catch / throw can
improve software quality? 
[Recently created (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes.]
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.
[ Top | Bottom | Previous section | Next section ]
[17.2] How can I handle a constructor that fails?
Throw an exception.
Constructors don't have a return type, so it's not possible to use return codes.
The best way to signal constructor failure is therefore to throw an exception.
If you don't have or won't use exceptions, here's a work-around. If a
constructor fails, the constructor can put the object into a "zombie" state.
Do this by setting an internal status bit so the object acts sort of like its
dead even though it is technically still alive. Then add a query ("inspector")
member function to check this "zombie" bit so users of your class can find
out if their object is truly alive, or if it's a zombie (i.e., a "living dead"
object). Also you'll probably want to have your other member functions check
this zombie bit, and, if the object isn't really alive, do a no-op (or perhaps
something more obnoxious such as abort()). This is really ugly, but it's the
best you can do if you can't (or don't want to) use exceptions.
[ Top | Bottom | Previous section | Next section ]
[17.3] How can I handle a destructor that fails? 
[Recently created (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes.]
Write a message to a log-file. Or call Aunt Tilda. But do not throw
an exception!
Here's why (buckle your seat-belts):
The C++ rule is that you must never throw an exception from a destructor that
is being called during the "stack unwinding" process of another exception.
For example, if someone says throw Foo(), the stack will be unwound
so all the stack frames between the throw Foo() and the
} catch (Foo e) { will get popped. This is called
stack unwinding.
During stack unwinding, all the local objects in all those stack frames are
destructed. If one of those destructors throws an exception (say it
throws a Bar object), the C++ runtime system is in a no-win situation:
should it ignore the Bar and end up in the
} catch (Foo e) { where it was originally headed? Should
it ignore the Foo and look for a } catch (Bar e) {
handler? There is no good answer -- either choice loses information.
So the C++ language guarantees that it will call terminate() at this
point, and terminate() kills the process. Bang you're dead.
The easy way to prevent this is never throw an exception from a
destructor. But if you really want to be clever, you can say never
throw an exception from a destructor while processing another
exception. But in this second case, you're in a difficult
situation: the destructor itself needs code to handle both throwing an
exception and doing "something else", and the caller has no guarantees as to
what might happen when the destructor detects an error (it might throw an
exception, it might do "something else"). So the whole solution is harder to
write. So the easy thing to do is always do "something else". That
is, never throw an exception from a destructor.
Of course the word never should be "in quotes" since there is always
some situation somewhere where the rule won't hold. But certainly at least
99% of the time this is a good rule of thumb.
[ Top | Bottom | Previous section | Next section ]
[17.4] How should I handle resources if my constructors may
throw exceptions?
Every data member inside your object should clean up its own mess.
If a constructor throws an exception, the object's destructor is not
run. If your object has already done something that needs to be undone (such
as allocating some memory, opening a file, or locking a semaphore), this "stuff
that needs to be undone" must be remembered by a data member inside the
object.
For example, rather than allocating memory into a raw Fred* data member, put
the allocated memory into a "smart pointer" member object, and the destructor
of this smart pointer will delete the Fred object when the smart pointer
dies. The standard class auto_ptr is an example of such as "smart pointer"
class. You can also write your own reference counting
smart pointer. You can also use smart pointers to
"point" to disk records or objects on other machines.
[ Top | Bottom | Previous section | Next section ]
[17.5] How do I change the string-length of an array of
char to prevent memory leaks even if/when someone throws an exception? 
[Recently rewrote the last half (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes.]
If what you really want to do is work with strings, don't use an array of
char in the first place, since arrays are evil.
Instead use an object of some string-like class.
For example, suppose you want to get a copy of a string, fiddle with the copy,
then append another string to the end of the fiddled copy. The array-of-char
approach would look something like this:
void userCode(const char* s1, const char* s2)
{
// Make a copy of s1:
char* copy = new char[strlen(s1) + 1];
strcpy(copy, s1);
// Now that we have a local pointer to freestore-allocated memory,
// we need to use a try block to prevent memory leaks:
try {
// ... now we fiddle with copy for a while...
// Append s2 onto the end of copy:
// ... [Here's where people want to reallocate copy] ...
char* copy2 = new char[strlen(copy) + strlen(s2) + 1];
strcpy(copy2, copy);
strcpy(copy2 + strlen(copy), s2);
delete[] copy;
copy = copy2;
// ... finally we fiddle with copy again...
} catch (...) {
delete[] copy; // Prevent memory leaks if we got an exception
throw; // Re-throw the current exception
}
delete[] copy; // Prevent memory leaks if we did NOT get an exception
}
Using char*s like this is tedious and error prone. Why not just use an
object of some string class? Your compiler probably supplies a string-like
class, and it's probably just as fast and certainly it's a lot simpler and
safer than the char* code that you would have to write yourself. For
example, if you're using the std::string class from the standardization committee, your code might look something like this:
#include <string> // Let the compiler see class std::string
void userCode(const std::string& s1, const std::string& s2)
{
std::string copy = s1; // Make a copy of s1
// ... now we fiddle with copy for a while...
copy += s2; // Append s2 onto the end of copy
// ... finally we fiddle with copy again...
}
That's a total of two (2) lines of code within the body of the function, as
compared with twelve (12) lines of code in the previous example. Most of the
savings came from memory management, but some also came because we didn't have
to explicitly call strxxx() routines. Here are some high
points:
- We do not need to explicitly write any code that
reallocates memory when we grow the string, since std::string handles memory
management automatically.
- We do not need to delete[] anything at the end, since
std::string handles memory management automatically.
- We do not need a try block in this second example, since
std::string handles memory management automatically, even if someone
somewhere throws an exception.
[ Top | Bottom | Previous section | Next section ]
E-mail the author
[ C++ FAQ Lite
| Table of contents
| Subject index
| About the author
| ©
| Download your own copy ]
Revised Jul 10, 2000
|