Click here to Skip to main content
15,879,326 members
Articles / Desktop Programming / MFC
Article

Developing applications that always decease gracefully.

Rate me:
Please Sign up or sign in to vote.
3.12/5 (41 votes)
9 Sep 200311 min read 122.2K   46   39
An article that describes how to write applications that always quit gracefully.

Introduction

It is always a dream for every programmer under the sun, to write programs that never smash. So this article presents some guidelines in trapping exceptions which can kill applications, and thereby ensuring the graceful exit of a program. I have included some code snippets that show how to trap exceptions.

Guidelines for writing C++ applications

Coping with object creation and destruction

  • Whenever you create an object in the heap (using the new operator), always remember to delete the same (using the delete operator).

    You can create an object of type T dynamically by using a new expression such as:

    T* obj = new T(); //creates a new object obj of type T in heap

    The original C++ rule says, new operator always returns a NULL pointer if memory could not be allocated. So it’s a good practice to check for NULL after creating the object, and before using it.

    When you want to deallocate a heap object, you can use the delete expression such as:

    delete obj; //Deletes the object obj from heap, and frees the memory

    Before deleting an object, always check for NULL. (This is because, may be you are trying to delete an object that is already deleted.)

    T* obj = new T(); //creates a new object obj of type T in heap
    if( obj != NULL )
    {
    // perform operations on obj
    }
    {
    if( obj != NULL )
    delete obj; //Deletes the object obj from heap, and frees the memory
    }

    All the member variables of a class should be created and initialized in the constructor itself. Most of the cases its not possible, so always ensure that objects are used only after they are initialized.

  • When creating an array of objects, you must have a constructor in place, that can be invoked without arguments.

    T* objs = new T[N]; //creates an array of N objects of type T.

    To deallocate the array, you must use this form of delete expression:

    delete [] objs;
  • Practice the habit of declaring virtual destructors in base classes.

    When you try to delete an object of a derived class through a pointer to a base class, only the base class destructor is executed, leading to all kinds of problems, like memory and other resource leaks.

    class A {
    . . .
    };
    class B : public A {
    . . .
    };
    A* pB = new B;
    delete pB;    // resource leak

    A common rule is that if a class has a virtual function, it probably needs a virtual destructor as well—and once we decide to pay the overhead of a vtable pointer, subsequent virtual functions will not increase the size of the object. So, in such a case, adding a virtual destructor doesn't add any significant overhead. So it’s always nice to have a virtual destructor in base classes.

Using Exceptions

When I started to use exceptions, it rapidly became evident that exceptions are more difficult to use effectively than it first seems. In fact, the more I explored how exceptions can interact with non-exception handling code, the more convinced I became that exceptions may be the most difficult feature of C++ to use. So there are some guidelines, which you should follow while using exceptions.

  • When you propagate an exception, try to leave the object in the state it had when the function was entered.

    This is the Golden Rule of exception handling. It is not going to do much good for a program to handle an exception if the program has gone into an invalid state as a result of the exception propagating to the point where it could be handled. I personally feel that writing code that meets this goal is very difficult. I put this guideline first because it is the target that should always be the ultimate goal. I will only give a few simple suggestions here.

    1. Make sure your const functions really are const.
    2. Perform exception prone operations early.
    3. Avoid side effects in expressions that might propagate exceptions.
    try {
    stack[esp++] = element; 
    } catch (...) {
    --esp; // reset state on exception
    }
  • If you cannot leave the object in the same state it had when the function was entered, try to leave it in a good state.

    The reason why this is required is that the client might not be paying as much attention to exception handling, as they should. Client might handle the exception, but not realize that the object is no longer valid. In such cases, always make sure that any further attempt to use the object will be rejected.

  • If you cannot leave the object in a "good" state, make sure the destructor will still work.

    If Guideline 1 and 2 are not possible, as a last resort, try to leave the object in such a state that it can always be safely destroyed. Never forget that as an exception propagates, it will unwind the stack frame of every function it propagates through. Many times, this will invoke the destructor of the very object that threw the exception.

  • Avoid resource leaks.

    The most obvious type of resource leak is a memory leak, but memory is not the only resource that can leak. In one sense, a resource leak is just another example of an inconsistent state. To avoid memory leaks, always try to use auto_ptr<> template class provided by the Standard C++ Library. There are three different instances where exceptions can cause resource leaks: in a constructor, in a destructor, and in a function (whether a member of a class or not).

    When an exception propagates from a constructor, the partial object that has been constructed so far is destroyed. If necessary, any memory allocated from the free store for the object is also released. If, during the construction, a resource (such as memory) is obtained directly and not as part of a sub-object whose destructor will release the resource, then a resource leak can occur. This situation can be dealt with using try block to catch the exception and attempting to release the memory. So initialize the pointers to NULL, and delete them all in the catch block.

    When we have resource leaks in destructors: As a general rule, we do not want to throw (or propagate) exceptions from destructors. Nevertheless, we cannot always prevent it. In situations like this, try using auto_ptr<>, and transfer ownership of the resources to the temporary objects. When the destructor body exits, these objects are destroyed, deleting their pointers.

  • Do not catch any exception you do not have to.

    Actually there is an old C rule of error handling, that states: "do not test for any error condition you do not know how to handle”. In C++ it means, it is a waste of time (yours and the computers) to catch an exception you do not know how to handle. I can give a few suggestions on how to behave on a situation like this.

    1. Always use a catch(...) block to cope with propagating exceptions.
    2. Do not "handle" any exception that cannot be "fixed."
    3. Do not throw exceptions from a destructor body.
    4. If you get stuck, call terminate() or exit().

    Before exceptions, the normal way to abnormally terminate a program was by calling the C library function abort(). In the new C++ world, we should call terminate() instead. terminate() just calls terminate_handler. In the default case, this calls abort(), but the user can replace the default terminate_handler with a program specific version. So call terminate() to allow any user defined terminate_handler to run.

  • Do not hide exception information from other parts of the program that might need it.

    The purpose of exceptions is to pass information from the point where an error is detectable to a point where the error can be handled. If you throw a different exception rather than re-throwing the original exception, you want to make sure you are increasing the possibility of the exception being handled, by doing so.

    1. Always re-throw the exception caught in a catch (...) clause.
    2. Re-throw a different exception only to increase the level of abstraction or capability.
    3. Make sure one catch block does not hide another.
  • Unless you have a Strong Guarantee of exception safety (Guideline 1), assume you must destroy the object after any exception.

    When you handle an exception, you correct whatever was wrong and then redo the failed operation. In order for this to work, you have to be sure that the operations which failed left their objects in the state they had before the operations were attempted. This is Guideline 1, and is what the C++ Standard calls the "Strong Guarantee". If you cannot say for certain that Guideline 1 is being followed, then all bets are off. If Guideline 2 has been followed, you might be in a position to reuse the object, but in general the only really safe thing to do is destroy the object and start over.

  • Always catch an exception by reference.

    This doesn’t require much clarification, because I assume that everyone already knows this.

    I presume, I have gone a bit detail into exceptions, so I am concluding this topic of exception-handling with a few words. “Exception handling is obviously a powerful feature of the C++ language. Although you can walk down the path of not using exceptions at all, their benefits far outweigh the costs”.

A little bit UNIX flavor

The UNIX operating system uses signals as a means of notifying a process that some event, often unrelated to the process's current activity, has occurred that requires the process' attention. Signals are delivered to a process asynchronously; a process cannot predict when a signal might arrive. Failing to properly handle various signals would likely cause your application to terminate, when it receives such signals. When we say that "Signals are being handled", we mean that our program is ready to handle such signals that the operating system might be sending it (such as signals notifying that the user asked to terminate it, or that a network connection we tried writing into, was closed, etc.). A typical signal handler would look like this:

/* This is the signal handler */
void catch_int(int sig_num)
{
/* re-set the signal handler again to catch_int, for next time */
signal(SIGINT, catch_int);
/* do some action */
fflush( stdout );
cout <<"some message"<<endl;
}

Now a few thumb rules for writing a signal handler.

  1. Make it short - the signal handler should be a short function that returns quickly. Instead of doing complex operations inside the signal handler, it is better that the function will raise a flag (e.g. a global variable, although these are evil by themselves) and have the main program check that flag occasionally.
  2. Proper Signal Masking - don't be too lazy to define proper signal masking for a signal handler, preferably using the sigaction() system call. It takes a little more effort than just using the signal() system call, but it'll help you sleep better at night, knowing that you haven't left an extra place for race conditions to occur.
  3. Careful with "fault" signals - If you catch signals that indicate a program bug (SIGBUS, SIGSEGV, SIGFPE), don't try to be too smart and let the program continue, unless you know exactly what you are doing (which is a very rare case) - just do the minimal required cleanup, and exit, preferably with a core dump (using the abort() function).
  4. Careful with timers - when you use timers, remember that you can only use one timer at a time, unless you also use the VTALRM signal. If you need to have more than one timer active at a time, don't use signals, or devise a set of functions that will allow you to have several virtual timers using a delta list of some sort.
  5. Signals are NOT an event driven framework - it is easy to get carried away and try turning the signals system into an event-driven driver for a program, but signal-handling functions were not meant for that. If you need such a thing, use some framework that is more suitable for the application.

In Windows, also we can trap for many different exceptions such as ACCESS_VIOLATION, STACK_OVERFLOW, ARRAY_BOUNDS_EXCEEDED, DATATYPE_MISALIGNMENT, FLT_DENORMAL_OPERAN, FLT_DIVIDE_BY_ZERO, FLT_INEXACT_RESULT, FLT_INVALID_OPERATION, FLT_OVERFLOW, FLT_STACK_CHECK, FLT_UNDERFLOW, INT_DIVIDE_BY_ZERO, and INT_OVERFLOW using the try- except blocks.

Using DEBUG functions

  • Using Assert

    It is not uncommon for bugs to linger because a section of code is not behaving as you think it is. I've stared at a line of code for minutes on end before I've been able to find a simple typo. More complicated bugs can be murder to find because, it often turns out, I'm assuming I see something that isn't there. One of the most powerful techniques for overcoming this problem is the Assert macro.

    Many compilers offer an Assert() macro as part of their default library. The Assert() macro is designed to assert a fact about your program-that is, to document (and test) what you think is true at a given moment in the history of your program. Here's how it works: You assert something by passing it as an argument to the Assert macro. The macro takes no action if you are correct, but it aborts your program and puts up an error message if you are not correct-if the asserted "fact" is not true.

    For example,

    Assert( x > 10 ) //If x is greater than 10, nothing happens
    
     //Otherwise program halts and an error message is displayed.

    The Assert macro is a powerful debugging tool, but for it to be acceptable in a professional development environment, it must not create a performance penalty nor increase the size of the executable version of the program. To accomplish this, the preprocessor collapses the Assert macro into no code at all if Debug is not defined. Thus, in your development environment, you can use Assert to find your bugs and misunderstandings, but when your code ships, there is no penalty.

  • Using Class Invariants

    Most classes have some conditions that should always be true. For example, it may be true that your Circle object should never have a radius of zero, or that your Animal should always have an age greater than 0 and less than 100. It can be very helpful to declare an Invariants() method that returns true only if all if these conditions are true. You can then Assert(Invariants()) at the start and completion of every class method.

Conclusion

"A robust program is resistant to errors -- it either works correctly, or it does not work at all; whereas a fault tolerant program must actually recover from errors".

Credits

While working on this, I have referred the following articles:

History

  • Date submitted: 05 September 2003.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
India India
I’ve got Bachelors and Masters degrees with honors in computer science. Now I am working as a technology consultant in XMinds. I love programming, no matter about the language, and I have done hard core programming in C++/VC/COM, and Java/J2EE.

Comments and Discussions

 
GeneralFinding / avoiding memory leaks [modified] Pin
Paul Sanders (the other one)20-Oct-07 6:27
Paul Sanders (the other one)20-Oct-07 6:27 
Generalchecking against NULL before deletion. Pin
satindar_kumar22-Mar-07 8:21
satindar_kumar22-Mar-07 8:21 
QuestionWorks fine with Vc7.1 but on Linux segmentation fault : can anybody tell why it is so? Pin
Member 462521730-Mar-05 4:07
Member 462521730-Mar-05 4:07 
AnswerRe: Works fine with Vc7.1 but on Linux segmentation fault : can anybody tell why it is so? Pin
Nigel Atkinson22-Feb-07 1:00
Nigel Atkinson22-Feb-07 1:00 
AnswerRe: Works fine with Vc7.1 but on Linux segmentation fault : can anybody tell why it is so? Pin
David Nash2-Jan-09 18:07
David Nash2-Jan-09 18:07 
GeneralSignal handlers and cleanup Pin
grmcdorman16-Sep-03 8:29
grmcdorman16-Sep-03 8:29 
Generalnew/delete do not behave as you describe Pin
grmcdorman16-Sep-03 8:04
grmcdorman16-Sep-03 8:04 
GeneralRe: new/delete do not behave as you describe Pin
Jagadeesh VN16-Sep-03 8:10
Jagadeesh VN16-Sep-03 8:10 
GeneralExceptions *and* new() Pin
Eric Anderton8-Sep-03 8:42
Eric Anderton8-Sep-03 8:42 
It takes some getting used to, but I've found that redefining new() to throw an exception instead of returning NULL can lead to more maintainable (if not more readable) code. This way, you can avoid having so many if...else constructs when grouping operations in a single try..catch is more appropriate. IMO, failure to allocate memory is almost always more of an exception-style case that may not always need explicit checking throughout the entire program.

// crude but effective example of a new() operator that throws.
void* operator new(size_t size)
{
  void* pMem = malloc(size);
  if(pMem == NULL) throw(CException("Failure to allocate memory."));
  return(pMem);
}


Of course, this may not be at all desireable if you're using new() as an initalizer for static data. In that case, rather than expecting a pointer's value to at least be NULL, it would be unitalized. (provided, of course that the CRT_init for the application somehow handles the exception gracefully)

- Eric

Calling a function is like tying your shoe. Remoting a function is like tying someone else's shoe... while they're running around... in China.
GeneralRe: Exceptions *and* new() Pin
Anna-Jayne Metcalfe11-Sep-03 3:32
Anna-Jayne Metcalfe11-Sep-03 3:32 
GeneralThanks for all your suggestions Pin
Jagadeesh VN6-Sep-03 0:07
Jagadeesh VN6-Sep-03 0:07 
GeneralCredit your sources... Pin
Stefan Pedersen5-Sep-03 14:39
Stefan Pedersen5-Sep-03 14:39 
GeneralRe: Credit your sources... Pin
Jagadeesh VN5-Sep-03 23:45
Jagadeesh VN5-Sep-03 23:45 
GeneralRe: Credit your sources... Pin
Anonymous17-Jan-05 4:37
Anonymous17-Jan-05 4:37 
GeneralThere should be more... Pin
KevinHall5-Sep-03 5:54
KevinHall5-Sep-03 5:54 
GeneralRe: There should be more... Pin
dog_spawn5-Sep-03 12:13
dog_spawn5-Sep-03 12:13 
GeneralNULLing after deleting Pin
Ravi Bhavnani5-Sep-03 5:50
professionalRavi Bhavnani5-Sep-03 5:50 
GeneralRe: NULLing after deleting Pin
dog_spawn5-Sep-03 12:11
dog_spawn5-Sep-03 12:11 
GeneralRe: NULLing after deleting Pin
Ravi Bhavnani5-Sep-03 12:20
professionalRavi Bhavnani5-Sep-03 12:20 
GeneralRe: NULLing after deleting Pin
dog_spawn5-Sep-03 12:26
dog_spawn5-Sep-03 12:26 
GeneralRe: NULLing after deleting Pin
Ravi Bhavnani5-Sep-03 12:29
professionalRavi Bhavnani5-Sep-03 12:29 
GeneralRe: NULLing after deleting Pin
Anna-Jayne Metcalfe11-Sep-03 3:29
Anna-Jayne Metcalfe11-Sep-03 3:29 
GeneralRe: NULLing after deleting Pin
PJ Arends5-Sep-03 23:53
professionalPJ Arends5-Sep-03 23:53 
GeneralRe: NULLing after deleting Pin
dog_spawn6-Sep-03 5:16
dog_spawn6-Sep-03 5:16 
GeneralRe: NULLing after deleting Pin
PFlorin8-Dec-04 3:37
PFlorin8-Dec-04 3:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.