See what's going on with flipcode!




 

C++ Exception Handling
by (08 August 2000)



Return to The Archives
Introduction


Exception handling can be a very personal and complex topic. The C language gave the programmer very few exception handling capabilities, as the programmers wanted more control over exceptions themselves. Thankfully, the C++ standards committee crafted a simple, but powerful form of exception handling for the C++ language that still gives the programmer quite a bit of control. Many coders eschew this method that I will present shortly, so more power to you. Most of the information I learned on C++ exception handling was from the wonderful Deep C++ column on MSDN.


Dark Days of C


A typical 'C' function may look something like this:


long DoSomething()
{
  long *a, c;
  FILE *b;
  a = malloc(sizeof(long) * 10);
  if (a == NULL)
	return 1;
  b = fopen("something.bah", "rb");
  if (b == NULL) {
	free(a);
	return 2;
  }
  fread(a, sizeof(long), 10, b);
  if (a[0] != 0x10) {
	free(a);
	fclose(b);
	return 3;
  }
  fclose(b);
  c = a[1];
  free(a);
  return c;
}
 


Quite messy if you ask me. You are extremely dependent on the return values of functions, and if an error occurs in the program, such as a header value not being correct, you have to constantly have code to handle this. If you allocate, say, 10 pointers in a function, I bet half of that function's code will be dedicated entirely to exception handling. Then there's the fact that if DoSomething returns an error code, the calling function will have to take appropriate steps to correctly handle the error, which, pardon my language, can be a huge pain in the ass.


Try-catch-throw


I'll show you the C++ version of the above function later on. I'm not going to just drop it in your lap and expect you to know everything (even if you do...). I will build up to it, starting with an explanation of try-catch-throw.
  • try - C++ keyword that denotes an exception block
  • catch - C++ keyword that "catches" exceptions
  • throw - C++ keyword that "throws" exceptions
  • Now for an example that should hopefully make their purpose evident:

    
    void func()
    {
      try
      {
        throw 1;
      }
      catch(int a)
      {
        cout << "Caught exception number:  " << a << endl;
        return;
      }
      cout << "No exception detected!" << endl;
      return;
    }
     


    If you were to run this snippet of code, the output would be:

    Caught exception number: 1

    Now, comment-out the throw 1; line, and the output will be:

    No exception detected!

    Okay, this seems extremely simple, but exception handling can be very powerful if used correctly. catch can catch any data type, and throw can throw any data type. So, throw dumbclass(); works, as does catch(dumbclass &d) { }.

    catch can catch any type, but it does not necessarily have to specify a variable. This is perfectly valid:

    
    catch(dumbclass) { }
     


    As is:

    
    catch(dumbclass&) { }
     


    Also, catch can catch every type of exception if need be:

    
    catch(...) { }
     


    The ellipses signify that all thrown exceptions are caught. You cannot specify a variable name for this method, though. I also recommend that if you catch'ing anything but a basic type (long, short, etc.), you catch them by reference, because, if you do not, the whole thrown variable has to be copied onto the stack, instead of just the reference to the thrown variable. If multiple variable types can be thrown, and you want to get the actual variables, not just an indicator that this type was thrown, you can do a multiple catch:

    
    try {
      throw 1;
    //  throw 'a';
    }
    catch (long b) {
      cout << "long caught:  " << b << endl;
    }
    catch (char b) {
      cout << "char caught:  " << b << endld;
    }
     


    Thrown Exceptions


    When an exception is thrown, the program keeps going up the function stack until it encounters an appropriate catch. If there is no catch statement in the program, STL will handle the thrown exception via the terminate handler, but it usually does so less gracefully than you could, by popping-up a nasty dialog box or any variety of other things, usually by calling abort.

    The absolute most important characteristic about this is that while it is stepping-up in the function stack, the program is calling the destructors of all local classes. So, voilą, you don't have to keep including code to free memory, etc. if you use classes often, even if they are just inline'd wrapper classes. I'd like to point you to http://www.boost.org right now. They have an excellent smart pointer class -- scoped_ptr, along with several other useful classes. It is pretty much equivalent to STL's auto_ptr, but I like it better. Either one is just fine to use.


    Overloading the Global New/Delete Operators


    I'd like to refer you to another tutorial now. The excellent "How To Find Memory Leaks" tutorial describes a great process to detect memory leaks by overloading the new and delete operators. The method he proposes only works for operator new/delete, not operator new[]/delete[]. It is fairly easy to figure these out, as they accept the same parameters.

    Okay, so you ask why I mention overloading these global operators in an exception handling tutorial, and an answer you shall receive. I modify Dion Picco's operator new/new[] to throw an exception if they fail in my projects. I also define non-debug versions. Source can be found for my implementation at the end of this tutorial. Now, I make my implementation of new/new[] throw my own exception class called "Exception", which is in exception.h in the source accompanying this tutorial. STL defines a class called "exception" in , but it deals exclusively with strings. There's no quick way to tell what kind of exception was caught by doing catch (exception &e) { } without doing a complete string compare. So I created my own class, which can work with either strings, numbers or both. For reference, the STL way is to use bad_alloc, which is in , but if you #include , you will have troubles, because the regular new/delete/new[]/delete[] prototypes are in there.

    If you overload new, etc. like mentioned, you can do:

    
    char *a;
    try
    {
      a = new char[10];
    }
    catch (...)
    {
      // a was not created - handle this here by exiting, etc.
    }
    // a was successfully created, continue
     


    Of course this seems like more code than the standard "is a equal to NULL?" The thing is, it's not, because you can call several functions in a try block that allocate memory or anything else that can throw an exception. The following example uses my CFile wrapper, but you can use whatever you want, even the iostream classes. Don't worry, as classes such as vector are very optimized, so it's just a myth that these classes induce a significant overhead. Now we can get back to our infamous DoSomething function, with a function that calls it:

    
    #include "file.h"
    #include <vector>
    #include "allocation.h"  // Must be included after headers that can allocate memory
    using std::vector;

    void CallDoSomething() { try { if (DoSomething()) { cout << "File had proper value!" << endl; } else { cout << "File had invalid value!" << endl; } } catch (Exception &e) { cout << "Memory not allocated!" << endl; } return; }

    bool DoSomething() { vector <int> a; CFile b;

    if (!b.Open("something.bah", "rb")) return false; a.resize(10); // If this fails, it throws an exception and closes b before leaving b.Read(&a[0], sizeof(long) * 10); if (a[0] != 0x10) { return false; } return true; }


    If you don't agree that this looks much nicer and is much easier to use than the C version, I don't know what is. =)

    Now we see that C++ exception handling's power lies within its ability to step out of the current function and automagically call any local class destructors along the way. If a critical error occurred in a regular C program, the coder might be forced to call exit() or abort(), but neither of these automatically calls the local class destructors on its way out of the program. In actuality, they don't even step up the function stack!


    Empty Throws


    So, we can see now how powerful and useful this "new" method is. A try-catch block can contain another try-catch block, and this block can throw anything, too, which will be caught by the outer catch if there is no corresponding inner catch, or the catch throws something. One thing I have neglected to mention is that throw need not even have anything after it:

    
    try
    {
      throw;
    }
    catch(...)
    {
      cout << "Caught exception!" << endl;
    }
     


    This could be used in extreme situations where you don't even care what was thrown.


    Application


    A practical application of using try-catch-throw can be in a game. Say you have a cMain class and instantiate a Main based off of it:

    
    class cMain
    {
    public:
      bool Setup();
      bool Loop();  // The main program loop
      void Close();
    };
    cMain Main;
     


    In your main() or WinMain() function, you can do something like:

    
    try
    {
      Main.Setup();
      Main.Loop();
      Main.Close();
    }
    catch (Exception &e)
    {
      log("Exception thrown:  %s", e.String());  // Just uses any log class you want.
    
      // Close everything down here and present an error message
    }
     


    Your cMain::Loop() function may look something like this:

    
    while (GameActive)
    {
      try
      {
        // Perform game loop here
      }
      catch (Exception &e)
      {
        /* Determine if exception thrown was critical, such as a memory error. 
        If the exception wasn't critical, just step out of this.  If it was,
        rethrow the exception like either "throw e" or just plain "throw", which
        will rethrow the caught exception. */
      }
    }
     


    Conclusion


    I've shown you to the best of my abilities how to use C++ exception handling, but only you can decide if you want to use it. The methods presented in this article can help speed development time and make your life a whole lot easier. There are a plenty of topics I failed to cover in this article, and if you want to learn them, I suggest you visit Deep C++.

     

    Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
    Please read our Terms, Conditions, and Privacy information.