Professional Documents
Culture Documents
Exceptions Include Iostream.h Int Main
Exceptions Include Iostream.h Int Main
introduced by ANSI-C++ standard. If you use a C++ compiler that is not adapted to this
standard it is possible that you cannot use this feature.
Their form of use is the following: try { // code to be tried throw exception; }
catch (type exception) { // code to be executed in case of exception }
For example:
The catch block must go right after the try block without including any
code line between them. The parameter that catch accepts can be of
any valid type. Even more, catch can be overloaded so that it can
accept different types as parameters. In that case the catch block
executed is the one that matches the type of the exception sent (the
parameter of throw):
1. That the required block of 10 characters cannot be assigned (something rare, but
possible): in this case an exception is thrown that will be caught by catch (char
* str).
2. That the maximum index for mystring is exceeded: in this case the exception
thrown will be caught by catch (int i), since the parameter is an integer number.
We can also define a catch block that captures all the exceptions
independently of the type used in the call to throw. For that we have to
write three points instead of the parameter type and name accepted
by catch: try { // code here } catch (...) { cout << "Exception occurred; }
If an exception is not caught by any catch statement because there is no catch statement
with a matching type, the special function terminate will be called.
Standard exceptions
Some functions of the standard C++ language library send exceptions that can be
captured if we include them within a try block. These exceptions are sent with a class
derived from std::exception as type. This class (std::exception) is defined in the C++
standard header file <exception and serves as a pattern for the standard hierarchy of
exceptions:
exception
"16linea2 =16 =16 ALIGNleft>bad_alloc (thrown by new)
(thrown by dynamic_cast when
"16linea2 =16 =16 ALIGNleft>bad_cast fails with a referenced type)
(thrown when an exception
"16linea2 =16 =16 ALIGNleft>bad_exception doesn't match any catch)
"16linea2 =16 =16 ALIGNleft>bad_typeid (thrown by typeid)
"16linea2 =16 =16 ALIGNleft>logic_error
"16linea1 =16 =16 ALIGNleft>"16linea2 =16 =16
ALIGNleft>domain_error
"16linea1 =16 =16 ALIGNleft>"16linea2 =16 =16
ALIGNleft>invalid_argument
"16linea1 =16 =16 ALIGNleft>"16linea2 =16 =16
ALIGNleft>length_error
"16linea1 =16 =16 ALIGNleft>"16linea3 =16 =16
ALIGNleft>out_of_range
"16linea2 =16 =16 ALIGNleft>runtime_error
"16linea1 =16 =16 ALIGNleft>"16linea2 =16 =16
ALIGNleft>overflow_error
"16linea1 =16 =16 ALIGNleft>"16linea2 =16 =16
ALIGNleft>range_error
"16linea1 =16 =16 ALIGNleft>"16linea3 =16 =16
ALIGNleft>underflow_error
"16linea3 =16 =16 ALIGNleft>ios_base::failure (thrown by ios::clear)
Because this is a class hierarchy, if you include a catch block to capture any of the
exceptions of this hierarchy using the argument by reference (i.e. adding an ampersand &
after the type) you will also capture all the derived ones (rules of inheritance in C++).
Introduction
Exceptions have been around in C++ for a while and are pretty
common. If you use the STL or even just new you have been exposed to
exceptions. Of course, like all things in C++, having a language feature
does not necessarily clearly point one in the direction of the best use of
that feature. I would not dare to call myself an expert or authority on
exception handling, but as a user of C++ I've had some exposure to
using exceptions.
This article will offer some insight into the use and potential misuse of
exceptions. I highly recommend reading Scott Meyer's More Effective
C++; there is a whole section devoted to exceptions. Bjarne
Stroustrup's The C++ Programming Language Third Edition also has
some excellent information on exceptions. I am assuming that the
reader is familiar with the basics of exception handling and hopefully
has read over Meyer's items on exceptions.
I also use a few terms from John Lakos's Large Scale C++ Software
Design. This book is invaluable for understanding how a logical design
should be translated into a collection of namespaces, header files,
source files and libraries. I mostly refer to components and packages.
try
{
if ( ! resourceAvail )
throw MyExceptionClass( "Resource Is Not Available" );
}
catch(MyExceptionClass& myException)
{
// resource was not available, do something cleanup
throw;
}
The code checks to see if a resource is available and if not, throws an
exception. The handler for the MyExceptionClass presumably does
something meaningful about the exceptional state. From this over-
simplified example we can see a typical use of exceptions which is to
prevent continued operation if the program cannot obtain the required
resource. Another example of this use of exceptions is new(), which will
throw the standard exception bad_alloc if the required amount of
memory is not available. (Unless, of course, you've changed the new
handler.)
This means there is additional code being executed. This can consist of
function calls that the compiler automatically inserts into the code.
Some compilers may allow this code to be generated inline to improve
performance, such as C++ Builder. So if we are probably going to be
paying this cost, then why worry about how many try/catch blocks
there are? Because it is best to partition error handling code from
regular execution code. This keeps the intent of the code clear.
try
{
SomeFunc();
AnotherFunc( 16 );
YetAnotherFunc( EvenMoreFunctions( 2 ) );
}
catch(...)
{
// catch all exceptions and cleanup
throw;
}
Exception Specifications
When I first started learning about exception specifications I flip-
flopped quite a bit about how useful they are. I could see the benefits
from a client perspective, but the implications imposed on the
component writer made me reconsider where exactly they should be
applied.
class MyClass
{
void MyMethodWithOnlyOneExceptionSpecification(void)
throw (MyExceptionClass);
void MyMethodThatThrowsNoExceptions(void) throw();
};
As a client of MyClass you can say, "Great! I only have to worry about
MyExceptionClass exceptions coming out of
MyMethodWithOnlyOneExceptionSpecification() and no exceptions will
be thrown from MyMethodWithThrowsNoExceptions()."
I've also defined exception classes which translate DirectX error codes
into a human readable message. If a component in the game engine
package throws an exception it is always of a type derived from the
base exception class of the package.
Also, since the exception classes themselves log the error message I
can make my catch blocks either catch-all handlers or for my base
exception class and still log exception information automatically. It is
probably best to use the specific handler in places where only your
own exception classes will be thrown and use the catch-all when
making calls into APIs which may be throwing anything; this second
case includes calling new() and its potential throw of bad_alloc. And I
generally have one catch handler for the try block in order to simplify
program flow control.
void Func(void)
{
auto_ptr myClass( new MyClass( ... parameters ... ) );
myClass->Something();
}
First is that each thread routine is akin to main() and you should not
allow exceptions to be thrown out of the thread routine. Provided you
created the thread using _beginthread(), and properly initialized the
runtime library, your application will exit indicating abnormal program
termination. Otherwise you will likely cause the operating system to
display an unsightly message about your application and offer to
terminate it for you. This is actually worse than throwing an exception
from main() which should just indicates abnormal program
termination.
Next is that each thread routine has its own stack, and hence
exception chain. This means that the current exception has to be per
thread data and re-throwing an exception is, obviously, only valid
within a single thread. The implication is if you have multiple threads
which may be working with the same component or group of
components and one causes an object to enter an invalid state due to
an exceptional condition, your other threads will probably not be aware
of this, even if you are properly synchronizing access.
One problem with throwing exceptions is that there may be other real-
time systems which are running concurrently and interdependently.
This may cause one of them to not stop properly. Not to mention the
issues involved with stopping a real-time system cleanly, particularly a
multi-threaded one.
The second use of a try/catch block is probably the most coherent use
of exception handling. At some level in the system we attempt to
perform a, usually, complex task which may fail in a number of ways.
In this use of a try/catch block we are only concerned with complete
success. No matter what the failure is we handle it in the exact same
manner. Informing the user of the cause of the error can still be
handled in a generic way, particularly if you have your own exception
hierarchy. Your base class of your exceptions can provide a method for
translating an exception into a human readable message that can be
presented to the user. This allows the user to know why the operation
failed and yet keeps the flow control simple in the error handler case.
Potential Pitfalls
Having run into a number of pitfalls in using exception handling, I'd like
to offer some things to watch out for. Mainly these focus around
understanding the system at run time and conceptualizing program
flow control.
Knowing when to re-throw an exception and when not to can
sometimes be difficult. It is far better to err on the side of caution and
always re-throw the exception unless you are a thread routine or high-
level component controlling a real-time system. This is at odds with
deciding whether or not you are handling an exception and are not re-
throwing it. The issue really hinges on the organization of your
components and the level in the system at which they exist. Generally
this means the top level components in the package or application
should have most of the exception handlers.
The reason being is that if you do not re-throw the exception you are
potentially introducing complex flow control that is not apparent when
inspecting the code. Although using the technique of logging each
exception on its construction can help alleviate this somewhat. This
tends to indicate that you should minimize exception handlers in low-
level components, using them only for freeing resources. When used
this way, they can be replaced with objects on the stack which perform
this resource management automatically.
A more thorny issue has to do with using multiple catch handlers for
different types of exceptions. It is important to remember that each
catch handler is a separate branch of execution. If you find yourself
doing different things based upon the type of exception you catch you
are walking down a potentially dangerous path. Basically, this amounts
to two bad things, one is case analysis of object type, generally
considered bad and using exceptions as a form of messaging.
The problem with case analysis is similar to other problems with case
analysis. If a new exception object is introduced into the system it may
need to have its own handler added and address the new 'case' of
exception. Even worse is that if the catch handlers do radically
different things then the application's behavior becomes dependent
upon what exception it catches. This leads us into the second problem.
Basically unless you write and maintain all the code that your try block
executes you cannot safely make assumptions about the origin of an
exception. Even if you do, the components are likely to be in different
locations and their flow control interactions will not be immediately
apparent. This can create a kind of pseudo-event handler mechanism
and the problem, in this context, with the event handler model is you
do not know the origin of the event, only the occurrence of it. It is far
safer to assume that you have no idea what specific exceptions may be
thrown from where, unless exception specifications are present, but
there's that whole can of worms.
Conclusion
Exception handling is obviously a powerful feature of the C++
language. Although you can walk down the path of not using
exceptions at al, their benefits far outweigh the costs. The key is to
understand how exceptions operate; how they interact in a game
application or other real-time system; where to use them and not use
them; and to understand their effect on performance. Exceptions can
greatly simplify the development of a C++ package and provide the
component writer a way to enforce the assumptions made when the
component was implemented.
- Steve Crocker
Virtual destructor
Implement all of your destructors as virtual if your class serves as a base class and will be
derived from. In other words, if there is a remote chance that other classes ever derive
from it make the destructor virtual. This way proper destruction of derived objects will
occur.