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


q;

q = p;           // p give ownership of resource to q

// when both go out of scope, only q destructor calls delete.


What this means is with respect to copying, the auto_ptr does not cut it. If you create more than one object from the class, you cannot perform any copying operations between the objects.

Frankly, the auto_ptr is a very weak implementation of a smart pointer. Designing a safe smart pointer class has given the C++ Standard committee tons of headaches. They probably ran out of time to design an all around useful smart pointer class (they have deadlines too), so we are stuck with auto_ptr.

What's the solution then? Well, you can either write your own or visit www.boost.org for a set of smart pointer classes that allow copying. You can then use these smart pointers safely in classes

Resource wrapper
Letís give a more realistic example. I will use the GetDC function from the Windows platform.

If you make a call to GetDC, the MSDN documentation states that the ReleaseDC function must be called to release the device context.

Now that you are getting paranoid about exception safety, you know that a potential leak will occur if the code between GetDC and ReleaseDC throws an exception.

What we need to do is to again make sure the GetDC/ReleaseDC calls are wrapped in a stack based wrapper class. A smart pointer cannot save us now, because the smart pointer only uses new and delete.

If you code in Windows or use any library, you usually come across an initialization and release resource function. These two functions are analogous to new and delete. They need to be wrapped up in a resource wrapper class like the smart pointer to ensure exception safety.

Unfortunately, the solution is to roll your own resource wrappers or use one from MFC. Personally, I would avoid MFC.

Exception class
The Base
Up to now, I have been throwing an int exception. As I said before, you can use throw any type of exception. So letís define a more useful error reporting class.

Example

class BaseException
{
public:
  BaseException( const char * const err )
  {
    strncpy( buf, err, 128 );
  }

  virtual const char * what()   // get the error string
  {
    return buf;
  }

protected:
  char buf[128];          // store the error string
};


I use a char buffer for simplicity, but I recommend using the STL string class to replace all the strings in your programs.

So now instead of throwing an int, we throw a BaseException object and pass a meaningful error string to the constructor. Later in the catch handler, we can retrieve the error string using the what() function.

Example

try
{
}
catch( BaseException &e )      // note we catch by reference
{
  cout << e.what() << endl;
}


Getting exceptions to work in Windows is the same as in a console program. Just wrap the main message pump function with a try/catch block.

What can we do with it?
Now we have a base exception class, we can use inheritance to create specific types of exceptions. Each derived class can correspond to a different type of error Ė File not found, Not enough memory, etc.

I will give two examples here Ė one for dealing with Windows errors and the other for handling SEH exceptions.

Windows exception class
I bet you have noticed Window functions use return codes to signal errors and GetLastError() to find out what the error was. Since we are coding in Windows, we have to adapt to their weak error handling implementation.

You can see the WindowsException class in the sample later on. It just translates the GetLastError code to a system message. Itís pretty simple to understand so you can just look at the source.

SEH Exception class
SEH in VC++ encompasses hardware exceptions too, but I bet you figured that out already. In any case, Microsoft 'suggestsí C++ exception handling to be used over SEH, and has kindly provided us with a means to translate SEH exceptions to C++ exceptions.

The function we are interested in is _set_se_translator. Whenever a hardware exception occurs, the registered translator function will be called. So what we do is throw a C++ exception within the translator function, like so

Example

// the translator function
void SEH_To_C++( unsigned int u, EXCEPTION_POINTERS *exp )
{
  throw int;        // throw an exception of type int
}


void main()
{
  try
  {
    _set_se_translator( SEH_To_C++ );  // register the translator

    // now all hardware exceptions will generate an C++ int
    // exception
  }
  catch(...)  {}
}


Note: _set_se_translator must be called for each separate thread.

Sample
Combining all your newfound knowledge, I present a simple sample with the exception class in action. You will see how we can incorporate and extend the base exception in the Windows environment.

I donít normally use MFC, but since Iím not getting paid for this, oh well, too bad for the code bloat. Donít blame me; Iím not the one who wrote the wizard.

The main points to note are

The main file you should look at is BaseException.h. It contains the aforementioned exception classes.
The entire main function is enclosed in a try/catch block, just like a console program.
I generate an exception when the mouse is moved a few times when the program is active. Of course the rest of the MFC Appwizard generated code is not exception safe, but Iím too lazy to fix it.
More! I want more!
I know you want more, so here it is

More information
You can store the line and source file where the exception occurred by passing in two additional parameters. Use the __FILE__, __LINE__ macros.

You can also do a stackwalk inside the exception class too. Take a look at the BugSlayer column by John Robbins at MSDN on how to do this.

Inherit from STL exception class
Instead of defining a new class by itself, your base exception class can be inherited from the STL exception class. This would allow you to catch exceptions from the standard library as well as your exceptions using one catch handler.

Evil Assert
Assertions are evil. No wait, I donít mean there are not useful, I meant the default assert implementation just calls abort, and that may not be exception safe.

I recommend throwing an exception instead when an assertion failure occurs. You can do this easily by extending the base exception class.

Extending it for DirectX
Many times, I see ingenious hacks being invented for error handling in DirectX. Personally, Iím guilty of those hacks too.

So, this is the latest hack using exceptions for a full screen DirectX application.

Extend another class that takes in a HRESULT. In that class, translate the passed result to a string using either the D3DX library or manually translating the error code. This is similar to the classes I presented.
In the catch handler of the main function, restore the video mode before displaying the MessageBox. This will ensure the Messagebox is always visible without a need to call FlipToGDI at all (which is a slow call).
If the DirectX function call fails, throw an exception.
If you have worked in full screen DirectX applications before, you will probably realize a MessageBox does not always show up. This is precisely the reason why I throw an exception for all the errors, even for assertion failures. When the exception reaches up to the main function, I restore the display mode and then I display the MessageBox describing the error.

Anyway, the above 'hackí is for DirectX 7.0. I havenít worked with DirectX 8 yet so I cannot guarantee if there are more 'hacksí needed.

Final Thoughts Ė Will this guy ever stop?
Yeah, yeah, soon enough. Letís go through some more details before I sign off.

Destructors cannot throw.
Destructors are meant for cleaning up and should never throw. If you find you need to throw in a destructor, itís best to redesign the class

No setjmp/longjmp/goto allowed.
This is probably for the better. If donít know what Iím talking about, good for you and move along.

Asynchronous Vs Synchronous exception handling.
Under asynchronous EH (exception handling), the compiler assumes that any line can throw an exception. Under synchronous EH, the compiler only assumes exceptions occurred where the programmer specify the throw.

What this means is that synchronous EH is more efficient for functions that donít throw exceptions, since the compiler donít generate code to keep track of the unwinding of the stack

Maybe Iím paranoid, but I like to ensure that even my release programs are exception safe, so I prefer async EH to sync EH.

As far as I can tell, sync EH might not be exception safe when a hardware exception occurs. If someone knows more about this, please enlighten me.

In VC++ 5.0, async EH was the default. In VC++ 6.0, sync EH is the default.

So if you want to enable async EH, look at the /EHa compiler flag.

Performance of exceptions.

Page : << Previous 3  Next >>