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.



Comments

  • Great one!

    Posted by Legacy on 08/14/2002 12:00am

    Originally posted by: Robbert E. Peters

    Great example!
    
    

    if you want to use enums you'll have can use the following:

    enum eMyError {
    E_NO_VAL=1,
    E_BAD_VAL,
    E_STOP_ALL
    } ;

    try {
    TestRoutine1(NULL);
    } catch (eMyError ret) {
    switch (ret) {
    case E_NO_VAL:
    MessageBox("No Variabe given");
    return;
    case E_BAD_VAL:
    MessageBox("Bad Variabe given");
    return;
    default:
    MessageBox("Unknown Error!!");
    throw(E_STOP_ALL);
    return;
    }
    }

    Thanks

    Rob

    Reply
  • What about C code

    Posted by Legacy on 08/14/2002 12:00am

    Originally posted by: Boaz Harrosh

    It is amazing how we all do the same things. I had just that problem the other day.
    I had to clean up an "MS-WORD" html file before I send it for merging into a report using XML-XSL. There is the "Tidy" application from the "W3". But now I want to do it on the fly (WORD is automated by my GUI) and hopefully I would like not to SPUN an external app and not to distribute the extra app. Well I have the code right?
    I don't want to C++ port the code or touch it in any way. I also hope to check new code from W3 as it evolves to meet new standards.
    But the code is full off exit()s . What to do?

    I included the Tidy code as-is into the project. Than at one of the Headers that is included in all the .c files I added:

    #define exit(S) *((char*)0) = 0 ;
    // this is in effect an access violation

    // I also needed to:
    #define main TidyMain

    Than At my CPP wrapper I just:
    Extern "C" int main (int ,char**) ;
    try {
    // Set up any parameters I need to pass to the main function.
    char* argv[] = {
    "tidy.exe" , // 0-parameter is the executable file
    filename ,"-c"
    // add any parameters here
    } ;
    argc = sizeof(argv) / sizeof (char*) ;
    main(argc ,argv) ;

    }catch(...) {
    // Exit was called
    }

    In C the __try-__finally-__leave statement will not work outside a function scope.
    The __try-__except is just the same as try{}catch(...) (catch 3 dots)

    This way I only need to add the two above lines to new code from W3 and compile my C++ windows GUI application.


    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Live Event Date: November 13, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT APIs can be a great source of competitive advantage. The practice of exposing backend services as APIs has become pervasive, however their use varies widely across companies and industries. Some companies leverage APIs to create internal, operational and development efficiencies, while others use them to drive ancillary revenue channels. Many companies successfully support both public and private programs from the same API by varying levels …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds