Topic : An Exceptional Quest
Author : Sobeit Void
Page : << Previous 2  Next >>
Go to page :


indicating success or failure. The caller will check the return value from calling other functions and it too will return a value indicating success or failure.

The above technique for error handling is bad because it requires the caller to check the return value of every call. The caller might not do that. You need to adopt the X-files philosophy Ė Trust no one. Besides, the function will become very messy when you have a lot more function calls.

Exceptions Ė A better solution?
Letís assume we do not use return values for error handling. Instead, we always throw an exception if we encounter an error. So the above example becomes

Example

void Function()              // notice we do not need return values
{
  Object *p = new Object();

  p->Some_Method();       // Some_Method() can return void

  if ( 1 != 0 )              // some fake error
    throw 100;               // if we encounter an error, always throw an
                             // exception like this
  delete p;

}


Notice we do not require the functions to return a value indicating success or failure. Since an exception is generated when an error occurs, we know the program flow will be disrupted so the caller must always handle the error (or crash the program).

Of course the above code is not exception safe, so letís rewrite it.

Example

void Function()        // note there is no return value
{
  Object *p = NULL;

  try
  {
    p = new Object();

    p->Some_Method();   // will throw an exception if error

    delete p;
  }
  catch(...)
  {
    delete p;
    throw;        // re-throw the exception to indicate error to caller
  }
}


That is now exception safe but still looks messy. At the very least, we eliminated testing of return values when calling other functions. Can we do better? You bet!

SideTrack - SEH
I go into a small sidetrack here. VC++ has a set of keywords __try,__finally,__except known as Structured Exception Handling (SEH). Itís basically error handling using exceptions for C programs.

The main limitation of SEH is that you are limited to using types that do not have destructors i.e. basic types and structs. However, you can mix functions that use SEH or C++ exceptions in your program.

Note that SEH is not portable but a VC++ specific implementation. Other compilers may support SEH but it is not guaranteed.

Example

void Function()
{
  Object_Struct *p = NULL;  // cannot have a destructor

  __try                     // note the double underscore
  {
    p = new Object_Struct();

    p->Some_Method();
  }
  __finally
  {
    delete p;         // always executed when function exit

  }
}


Code in the finally section is always executed when the function exits.

Just thought itís a nice trick to know when working with C code only. I will not go into further details on SEH. The MSDN docs have a nice section on SEH if you need to know more.

The Ultimate Solution - Smart Pointer
Letís recap where we are just in case you are lost. We want to use exceptions for error reporting instead of using return values. We also want to able to create heap objects but still have exception safety.

I said earlier about evil pointers and stack objects are always exception safe. This is the solution for our problem: a stack based pointer, commonly known as a smart pointer.

The main ideas behind smart pointers are to wrap the heap object in another stack based object (the smart pointer). The stack wrapper object is exception safe because it will be de-allocated when the function exits. Since it wraps the heap object, the heap object is cleaned up as well (in the smart pointer destructor).

The implementation of a smart pointer is actually quite advanced and requires the use of templates but all we need to know is that the destructor of the smart pointer will call delete on the heap pointer.

Letís look at it in action to understand more about it. I use a smart pointer implementation from the STL (Standard Template Library) known as the auto_ptr. Of course, you can always roll your own smart pointer classes.

Example

#include <memory>       // include auto_ptr definition

void Function()
{
  std::auto_ptr<Object> p( new Object() );

  p->Some_Method();
}


Thatís it. The above code is exception safe and p is allocated on the heap. Contrast the elegance of this code with all the previous solutions.

Letís look at the smart pointer in more detail. Other than the ugly looking namespace syntax, notice that we declared a stack object of class auto_ptr. We then tell the smart pointer class the type of pointer is it meant to wrap.

std::auto_ptr<Class Type>

We then declare an instance of the smart pointer using a default constructor and use the smart pointer as if it was the original pointer. Isnít that neat??

This looks like a good time for some real code, so letís see the smart pointer in action. Take a look at the sample before continuing.

Smart Pointer Problems
Wait a minute. Arenít smart pointers the ultimate solutions? Yes, but there is a very big issue you must be aware of.

A smart pointer class calls delete on itís wrapped pointer, not delete[], so you cannot use it for arrays.

std::auto_ptr<Object>  p( new Object[100] );  // BAD, NO ARRAYS

Try to do the above and you are shooting yourself in the foot.

What happens if you need an exception safe array? Use the vector class.

std::vector<Object> v(100);

v[0] = 1;          // use like an array
v[2] = 2;


What if you really need to use a pointer? This is mostly the case when you are working with legacy C code. Then, Iím sorry, but you have to resort to one of the messier solutions to ensure exception safety. After that, learn to really hate old C code.

If you need to know more about the STL and the auto_ptr, vector class etc. and how it can save you, go get a book. I recommend The C++ Standard Library by Nicolai M. Josuttis. No, Iím not getting paid for this, though I sure wish I were.

Scenario 2 - Exception Safety in classes & Resource wrapping
Now that we know that we should not be using raw pointers but always use smart pointers instead, letís look at how it should be done in classes

Example

class TestClass           // A classic class
{
  // sniped
private:
  Object* p1;             // assume Object class defined
  Object* p2;
};


Using raw pointers for classes is not exception safe if your class uses more than one pointer because in the constructor, the allocation between the two pointers may fail.

Example

TestClass::TestClass()        // Classic constructor
{
  p1 = new Object();

  // if some exception occurs here, then p1 is never freed and a memory leak occurs again
  p2 = new Object();
}


The solution? Smart pointers to the rescue.

Example

class TestClass   // exception safe class
{
public:

  // Constructor
  TestClass() : p1( new Object() )       // use initization list
  {
    // delay load the smart pointer
    p2 = std::auto_ptr<Object>( new Object() );
  }

private:
  std::auto_ptr<Object> p1;      // smart pointer
  std::auto_ptr<Object> p2;
};


Now you think you are smart enough to take on the world, but let me tell you there is another problem with the above code.

The problem lies with the smart pointer.

Remember the problem of shallow and deep copy in classes? If you have raw pointers, you need to define the copy constructor and assignment operator or only the pointers will be copied due to the default implementation of copy constructor and assignment operator doing bitwise copying.

We have a similar problem with the auto_ptr.

Ownership of the resource
The auto_ptr specifies that there can be only one owner on the resource it wraps at any one time. If you do an assignment, the ownership is transferred and delete will not be called on the original auto_ptr.

Example

std::auto_ptr<Object> p( new Object() );
std::auto_ptr<Object>


Page : << Previous 2  Next >>