Using C++ Exceptions to Replace exit()

Environment: C/C++

The same task seems to return to me from time to time as I have to deal with legacy C/C++ code. It seems there existed developers who shared the opinion that calling the function exit as soon as there is an error or when the work is done is the best-suited method to end a program. As projects evolve, they tend to merge formerly separate modules. It even happens that someone remembers that it would be nice to include logging, fault tolerance, or at least proper cleanup. Before moving on, please let me make myself clear that the technique I am bringing to your attention is by no means a design guideline, but rather the least painful way to fix legacy code that has been already poorly designed and implemented.

Replacing the exit function with return and bubbling up the return code is the most obvious way to solve the problem. If the project is simple, this can be the most effective solution. However, projects with dozens of functions spread across multiple source files with calls nested many levels deep are nothing exceptional. In the unlikely case when all these functions return void, it is still possible to redo them and return an exit code. This already becomes expensive. If the functions already return meaningful values and call exit when they encounter an error, the task becomes much more time consuming and highly prone to errors. There are other cases when the approach can help, as when it is necessary to get the return code from functions that have never been designed to return any.

There is a possible solution to the problem, in the case where the original source was already in C++ or it can be ported to C++ without major complications. Replace all occurences of exit with throw. This can be done automatically, without even the need to understand exactly how the old code works. Wherever appropriate, catch the integer exception code. The technique provides the additional advantage that it is possible to handle errors at different levels depending on their severity and the result of recovery attempts.

Here is an example based on a real experience. The original might have looked like this:


// main.cpp
void main() {
// initialize

ProcessMail(…);
}

// another source
void ProcessMail(…) {
// initialize

if ( initializationError ) {
printf(“faild to init!!!n”);
exit(-1);
}
while ( !shutdown ) {
ReadMail(…)
// process further

}
}

void ReadMail(…)
{

// The call to ReadBytes() occurs at multiple locations
// within the function, also in loops… now you get the figure

nBytesAvailable = ReadBytes(…)

}

// yet another source file
int ReadBytes(…)
{
// Read data

if ( error ) {
printf(“there was an error!!n”);
exit(-1);
}
return nBytesRead;
}

The program ran in the context of a system service (now these are officially called Windows Services). The original code lacked any recovery or logging features. If an error occurred, the process would disappear, leaving customers and support, uhm, very unhappy. Reorganized, the code looks like this (notice there are no changes in function signatures):


void main() {
// initialize

try {
ProcessMail(…);
} catch (int ret) {
switch (ret) {
case E_INITIALIZATION_FAILURE: …
case E_IRRECOVERABLE: …

}
}
}

void ProcessMail(…) {
// initialize

if ( initializationError ) {
throw(E_INITIALIZATION_FAILURE);
}

while ( !shutdown ) {
try {
ReadMail(…)
} catch (int ret) {
switch (ret) {
case E_READ_ERROR:
// Log error information

// attempt to recover

if ( recovered ) {
continue;
} else {
throw(E_IRRECOVERABLE);
}
break;
case …
}
}
// process further

}

// throw() COULD be used instead of the missing return code
// but needs additional consideration as it involves serious
// performance penalties

throw(S_OK);
} // ProcessMail()

void ReadMail(…)
{

// no need to catch exceptions here
nBytesAvailable = ReadBytes(…)

}

int ReadBytes(…)
{
// Read data
if ( error ) {
throw(E_READ_ERROR);
}
return nBytesRead;
}

Bonus idea. Look for atexit in the documentation. This can give you extra muscle when you deal with legacy projects.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read