Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Traditional error handling

In most of the examples in these volumes, we use assert( ) as it was intended: for debugging during development with code that can be disabled with #define NDEBUG for the shipping product. Runtime error checking uses the require.h functions (assure( ) and require( )) developed in Chapter 9 in Volume 1 and repeated here in Appendix B. These functions are a convenient way to say, There s a problem here you ll probably want to handle with some more sophisticated code, but you don t need to be distracted by it in this example. The require.h functions might be enough for small programs, but for complicated products you ll want to write more sophisticated error-handling code.

Error handling is quite straightforward when you know exactly what to do, because you have all the necessary information in that context. You can just handle the error at that point.

The problem occurs when you don t have enough information in that context, and you need to pass the error information into a different context where that information does exist. In C, you can handle this situation using three approaches:

1.      Return error information from the function or, if the return value cannot be used this way, set a global error condition flag. (Standard C provides errno and perror( ) to support this.) As mentioned earlier, the programmer is likely to ignore the error information because tedious and obfuscating error checking must occur with each function call. In addition, returning from a function that hits an exceptional condition might not make sense.

2.      Use the little-known Standard C library signal-handling system, implemented with the signal( ) function (to determine what happens when the event occurs) and raise( ) (to generate an event). Again, this approach involves high coupling because it requires the user of any library that generates signals to understand and install the appropriate signal-handling mechanism. In large projects the signal numbers from different libraries might clash.

3.      Use the nonlocal goto functions in the Standard C library: setjmp( ) and longjmp( ). With setjmp( ) you save a known good state in the program, and if you get into trouble, longjmp( ) will restore that state. Again, there is high coupling between the place where the state is stored and the place where the error occurs.

When considering error-handling schemes with C++, there s an additional critical problem: The C techniques of signals and setjmp( )/longjmp( ) do not call destructors, so objects aren t properly cleaned up. (In fact, if longjmp( ) jumps past the end of a scope where destructors should be called, the behavior of the program is undefined.) This makes it virtually impossible to effectively recover from an exceptional condition because you ll always leave objects behind that haven t been cleaned up and that can no longer be accessed. The following example demonstrates this with setjmp/longjmp:

//: C01:Nonlocal.cpp
// setjmp() & longjmp().
#include <iostream>
#include <csetjmp>
using namespace std;
 
class Rainbow {
public:
Rainbow() { cout << "Rainbow()" << endl; }
~Rainbow() { cout << "~Rainbow()" << endl; }
};
 
jmp_buf kansas;
 
void oz() {
Rainbow rb;
for(int i = 0; i < 3; i++)
cout << "there's no place like home" << endl;
longjmp(kansas, 47);
}
 
int main() {
if(setjmp(kansas) == 0) {
cout << "tornado, witch, munchkins..." << endl;
oz();
} else {
cout << "Auntie Em! "
<< "I had the strangest dream..."
<< endl;
}
} ///:~
 

The setjmp( ) function is odd because if you call it directly, it stores all the relevant information about the current processor state (such as the contents of the instruction pointer and runtime stack pointer) in the jmp_buf and returns zero. In this case it behaves like an ordinary function. However, if you call longjmp( ) using the same jmp_buf, it s as if you re returning from setjmp( ) again you pop right out the back end of the setjmp( ). This time, the value returned is the second argument to longjmp( ), so you can detect that you re actually coming back from a longjmp( ). You can imagine that with many different jmp_bufs, you could pop around to many different places in the program. The difference between a local goto (with a label) and this nonlocal goto is that you can return to any pre-determined location higher up in the runtime stack with setjmp( )/longjmp( ) (wherever you ve placed a call to setjmp( )).

The problem in C++ is that longjmp( ) doesn t respect objects; in particular it doesn t call destructors when it jumps out of a scope.[1] Destructor calls are essential, so this approach won t work with C++. In fact, the C++ Standard states that branching into a scope with goto (effectively bypassing constructor calls), or branching out of a scope with longjmp( ) where an object on the stack has a destructor, constitutes undefined behavior.

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire