Home | | Object Oriented Programming | Exception handling - C++

Chapter: Object Oriented Programming(OOP) : Advanced Programming

Exception handling - C++

Exception handling is a construct designed to handle the occurrence of exceptions, that is special conditions that changes the normal flow of program execution. Since when designing a programming task (a class or even a function), one cannot always assume that application/task will run or be completed correctly (exit with the result it was intended to).

EXCEPTION HANDLING

 

 

Consider this next code example of a try and catch block combination for clarification:

Exception handling is a construct designed to handle the occurrence of exceptions, that is special conditions that changes the normal flow of program execution. Since when designing a programming task (a class or even a function), one cannot always assume that application/task will run or be completed correctly (exit with the result it was intended to). It may be the case that it will be just inappropriate for that given task to report an error message (return an error code) or just exit. To handle these types of cases, C++ supports the use of language constructs to separate error handling and reporting code from ordinary code, that is, constructs that can deal with these exceptions (errors and abnormalities) and so we call this global approach that adds uniformity to program design the exception handling.

 

An exception is said to be thrown at the place where some error or abnormal condition is detected. The throwing will cause the normal program flow to be aborted, in a raised exception. An exception is thrown programmatic, the programmer specifies the conditions of a throw.

 

In handled exceptions, execution of the program will resume at a designated block of code, called a catch block, which encloses the point of throwing in terms of program execution. The catch block can be, and usually is, located in a different function/method than the point of throwing. In this way, C++ supports non-local error handling. Along with altering the program flow, throwing of an exception passes an object to the catch block. This object can provide data that is necessary for the handling code to decide in which way it should react on the exception.

 

Consider this next code example of a try and catch block combination for clarification:

void AFunction()

 

{

//  This function does not return normally,

//  instead execution will resume at a catch block.

//  The thrown object is in this case of the type char const*,

//  i.e. it is a C-style string. More usually, exception

//  objects are of class type.

 

throw "This is an exception!";

}

 

void AnotherFunction()

{

//  To catch exceptions, you first have to introduce

//  a try block via " try { ... } ". Then multiple catch

//  blocks can follow the try block.

 

//  " try { ... } catch(type 1) { ... } catch(type 2) { ... }" try

 

{

AFunction();

 

 

//  Because the function throws an exception,

//  the rest of the code in this block will not

//  be executed

}

catch(char const* pch)  // This catch block

//  will react on exceptions

//  of type char const*

{

//  Execution will resume here.

//  You can handle the exception here.

}

// As can be seen

catch(...) // The ellipsis indicates that this

 

// block will catch exceptions of any type.

{

//  In this example, this block will not be executed,

//  because the preceding catch block is chosen to

//  handle the exception.

}

}

 

Unhandled exceptions on the other hand will result in a function termination and the stack will be unwound (stack allocated objects will have destructors called) as it looks for an exception handler. If none is found it will ultimately result in the termination of the program.

 

From the point of view of a programmer, raising an exception is a useful way to signal that a routine could not execute normally. For example, when an input argument is invalid (e.g. a zero denominator in division) or when a resource it relies on is unavailable (like a missing file, or a hard disk error). In systems without exceptions, routines would need to return some special error code. However, this is sometimes complicated by the semi-predicate problem, in which users of the routine need to write extra code to distinguish normal return values from erroneous ones.

 

Because it is hard to write exception safe code, you should only use an exception when you have to—when an error has occurred that you can not handle. Do not use exceptions for the normal flow of the program.

 

This example is wrong, it is a demonstration on what to avoid: void sum(int iA, int iB)

 

{

throw iA + iB;

 

}

 

int main()

{

int iResult;

 

try

{

sum(2, 3);

 

}

catch(int iTmpResult)

{

//  Here the exception is used instead of a return value!

//  This is wrong!

iResult = iTmpResult;

}

 

return 0;

}

Stack unwinding

 

Consider the following code void g()

 

{

throw std::exception();

}

 

void f()

{

 

std::string str = "Hello"; // This string is newly allocated g();

 

}

 

int main()

{

try

{

f();

}

 

catch(...) { }

}

The flow of the program:

main() calls f()

f() creates a local variable named str

str constructor allocates a memory chunk to hold the string "Hello"

f() calls g()

g()throws an exception

f() does not catch the exception.

Because the exception was not caught, we now need to exit f() in a clean fashion. At this point, all the destructors of local variables previous to the throw

 

are called—This is called 'stack unwinding'.

The destructor of str is called, which releases the memory occupied by it.

As you can see, the mechanism of 'stack unwinding' is essential to prevent resource leaks—without it, str would never be destroyed, and the memory it used would be lost

 

 

until the end of the program (even until the next loss of power, or cold boot depending on the Operative System memory management).

 

main() catches the exception

The program continues.

The 'stack unwinding' guarantees destructors of local variables (stack variables) will be called when we leave its scope.

 

Throwing objects

 

There are several ways to throw an exception object. Throw a pointer to the object:

 

void foo()

{

throw new MyApplicationException();

}

 

void bar()

{

try

{

foo();

}

catch(MyApplicationException* e)

{

// Handle exception

}

}

 

But now, who is responsible to delete the exception? The handler? This makes code uglier. There must be a better way!

 

How about this: void foo()

 

{

throw MyApplicationException();

}

void bar()

{

 

try

{

foo();

 

}

catch(MyApplicationException e)

{

// Handle exception

}

}

 

But now, the catch handler that catches the exception, does it by value, meaning that a copy constructor is called. This can cause the program to crash if the exception caught was a bad_alloc caused by insufficient memory. In such a situation, seemingly safe code that is assumed to handle memory allocation problems results in the program crashing with a failure of the exception handler. Moreover, catching by value may cause the copy to have different behavior because of object slicing.

 

The correct approach is: void foo()

 

{

throw MyApplicationException();

}

 

void bar()

{

try

{

 

foo();

}

catch(MyApplicationException const& e)

{

// Handle exception

}

}

 

This method has all the advantages—the compiler is responsible for destroying the object, and no copying is done at catch time!

 

The conclusion is that exceptions should be thrown by value, and caught by (usually const) reference.

 

1. Constructors and destructors

 

When an exception is thrown from a constructor, the object is not considered instantiated, and therefore its destructor will not be called. But all destructors of already successfully constructed base and member objects of the same master object will be called. Destructors of not yet constructed base or member objects of the same master object will not be executed. Example: class A : public B, public C

 

{

public:

D sD;

E sE;

 

A(void)

:B(), C(), sD(), sE()

{

}

};

Let's assume the constructor of base class C throws. Then the order of execution is:

B

C (throws)

~B

Let's assume the constructor of member object sE throws. Then the order of execution is:

B

C

 

sD

sE (throws)

~sD

~C

~B

Thus if some constructor is executed, one can rely on that all other constructors of the same master object executed before, were successful. This enables one, to use an already constructed member or base object as an argument for the constructor of one of the following member or base objects of the same master object.

 

What happens when we allocate this object with new?

Memory for the object is allocated

The object's constructor throws an exception

o The object was not instantiated due to the exception The memory occupied by the object is deleted

The exception is propagated, until it is caught

The main purpose of throwing an exception from a constructor is to inform the program/user that the creation and initialization of the object did not finish correctly. This is a very clean way of providing this important information, as constructors do not return a separate value containing some error code (as an initialization function might).

 

In contrast, it is strongly recommended not to throw exceptions inside a destructor. It is important to note when a destructor is called:

 

as part of a normal deallocation (exit from a scope, delete)

as part of a stack unwinding that handles a previously thrown exception.

 

In the former case, throwing an exception inside a destructor can simply cause memory leaks due to incorrectly deallocated object. In the latter, the code must be more clever. If an exception was thrown as part of the stack unwinding caused by another exception, there is no way to choose which exception to handle first. This is interpreted as a failure of the exception handling mechanism and that causes the program to call the function terminate.

 

To address this problem, it is possible to test if the destructor was called as part of an exception handling process. To this end, one should use the standard library function uncaught_exception, which returns true if an exception has been thrown, but hasn't been caught yet. All code executed in such a situation must not throw another exception.

 

Situations where such careful coding is necessary are extremely rare. It is far safer and easier to debug if the code was written in such a way that destructors did not throw exceptions at all.

 

Writing exception safe code Exception safety

 

A piece of code is said to be exception-safe, if run-time failures within the code will not produce ill effects, such as memory leaks, garbled stored data, or invalid output. Exception-safe code must satisfy invariants placed on the code even if exceptions occur. There are several levels of exception safety:

 

1.     Failure transparency, also known as the no throw guarantee: Operations are guaranteed to succeed and satisfy all requirements even in presence of exceptional situations. If an exception occurs, it will not throw the exception further up. (Best level of exception safety.)

 

 

 

2.     Commit or rollback semantics, also known as strong exception safety or no-change guarantee: Operations can fail, but failed operations are guaranteed to have no side effects so all data retain original values.

 

3.     Basic exception safety: Partial execution of failed operations can cause side effects, but invariants on the state are preserved. Any stored data will contain valid values even if data has different values now from before the exception.

 

4.     Minimal exception safety also known as no-leak guarantee: Partial execution of failed operations may store invalid data but will not cause a crash, and no resources get leaked.

 

5.     No exception safety: No guarantees are made. (Worst level of exception safety)

 

 

 

2. Partial handling

 

Consider the following case: void g()

 

{

 

throw "Exception";

}

 

void f()

{

 

int* pI = new int(0); g();

 

delete pI;

}

 

int main()

{

 

f(); return 0;

}

 

Can you see the problem in this code? If g() throws an exception, the variable pI is never deleted and we have a memory leak.

 

To prevent the memory leak, f() must catch the exception, and delete pI. But f() can't handle the exception, it doesn't know how!

 

What is the solution then? f() shall catch the exception, and then re-throw it: void g()

 

{

throw "Exception";

}

 

void f()

{

int* pI = new int(0);

 

try

{

g();

}

 

 

catch (...)

{

delete pI;

 

throw; // This empty throw re-throws the exception we caught // An empty throw can only exist in a catch block

 

}

 

delete pI;

}

 

int main()

{

 

f(); return 0;

}

 

There's a better way though; using RAII classes to avoid the need to use exception handling. Guards

 

If you plan to use exceptions in your code, you must always try to write your code in an exception safe manner. Let's see some of the problems that can occur:

 

Consider the following code: void g()

 

{

throw std::exception();

}

 

void f()

{

int* pI = new int(2);

 

*pI = 3; g();

 

//  Oops, if an exception is thrown, pI is never deleted

//  and we have a memory leak

delete pI;

 

}

 

int main()

 

{

try

{

f();

}

 

catch(...) { }

 

return 0;

 

 

}

 

Can you see the problem in this code? When an exception is thrown, we will never run the line that deletes pI!

 

What's the solution to this? Earlier we saw a solution based on f() ability to catch and re-throw. But there is a neater solution using the 'stack unwinding' mechanism. But 'stack unwinding' only applies to destructors for objects, so how can we use it?

 

We can write a simple wrapper class:

 

// Note: This type of class is best implemented using templates, discussed in the next chapter. class IntDeleter {

 

public:

IntDeleter(int* piValue)

{

m_piValue = piValue;

 

}

 

~IntDeleter()

{

delete m_piValue;

}

 

//  operator *, enables us to dereference the object and use it

//  like a regular pointer.

int& operator *()

{

return *m_piValue;

}

 

private:

int* m_piValue;

};

 

The new version of f(): void f()

 

{

IntDeleter pI(new int(2));

 

*pI = 3; g();

 

//  No need to delete pI, this will be done in destruction.

//  This code is also exception safe.

}

 

The pattern presented here is called a guard. A guard is very useful in other cases, and it can also help us make our code more exception safe. The guard pattern is similar to a finally block in other languages.

 

Note that the C++ Standard Library provides a templated guard by the name of auto_ptr. Exception hierarchy

 

You may throw as exception an object (like a class or string), a pointer (like char*), or a primitive (like int). So, which should you choose? You should throw objects, as they ease the handling of exceptions for the programmer. It is common to create a class hierarchy of exception classes:

 

class MyApplicationException {};

·        class MathematicalException : public MyApplicationException {};

·        class DivisionByZeroException : public MathematicalException {};

·         class InvalidArgumentException : public MyApplicationException {};

 

An example:

float divide(float fNumerator, float fDenominator)

{

if (fDenominator == 0.0)

{

 

throw DivisionByZeroException();

}

 

return fNumerator/fDenominator;

}

 

enum MathOperators {DIVISION, PRODUCT};

 

float operate(int iAction, float fArgLeft, float fArgRight)

{

if (iAction == DIVISION)

{

return divide(fArgLeft, fArgRight);

}

else if (iAction == PRODUCT))

{

//  call the product function

//  ...

}

 

// No match for the action! iAction is an invalid agument throw InvalidArgumentException();

 

}

 

int main(int iArgc, char* a_pchArgv[])

{

try

{

operate(atoi(a_pchArgv[0]), atof(a_pchArgv[1]), atof(a_pchArgv[2]));

}

catch(MathematicalException& )

{

// Handle Error

 

 

}

catch(MyApplicationException& )

{

//  This will catch in InvalidArgumentException too.

//  Display help to the user, and explain about the arguments.

}

 

return 0;

}

 

3. Exception specifications

 

The range of exceptions that can be thrown by a function are an important part of that function's public interface. Without this information, you would have to assume that any exception could occur when calling any function, and consequently write code that was extremely defensive. Knowing the list of exceptions that can be thrown, you can simplify your code since it doesn't need to handle every case.

 

This exception information is specifically part of the public interface. Users of a class don't need to know anything about the way it is implemented, but they do need to know about the exceptions that can be thrown, just as they need to know the number and type of parameters to a member function. One way of providing this information to clients of a library is via code documentation, but this needs to be manually updated very carefully. Incorrect exception information is worse than none at all, since you may end up writing code that is less exception-safe than you intended to.

 

C++ provides another way of recording the exception interface, by means of exception specifications. An exception specification is parsed by the compiler, which provides a measure of automated checking. An exception specification can be applied to any function, and looks like this:

 

double divide(double dNumerator, double dDenominator) throw (DivideByZeroException);

 

You can specify that a function cannot throw any exceptions by using an empty exception specification:

 

void safeFunction(int iFoo) throw(); Shortcomings of exception specifications

 

C++ does not programmatically enforce exception specifications at compile time. For example, the following code is legal:

 

void DubiousFunction(int iFoo) throw()

 

{

if (iFoo < 0)

{

 

throw RangeException();

}

}

 

Rather than checking exception specifications at compile time, C++ checks them at run time, which means that you might not realize that you have an inaccurate exception specification until testing or, if you are unlucky, when the code is already in production.

 

If an exception is thrown at run time that propagates out of a function that doesn't allow the exception in its exception specification, the exception will not propagate any further and instead, the function RangeException() will be called. The RangeException() function doesn't return, but can throw a different type of exception that may (or may not) satisfy the exception specification and allow exception handling to carry on normally. If this still doesn't recover the situation, the program will be terminated.

 

Many people regard the behavior of attempting to translate exceptions at run time to be worse than simply allowing the exception to propagate up the stack to a caller who may be able to handle it. The fact that the exception specification has been violated does not mean that the caller can't handle the situation, only that the author of the code didn't expect it. Often there will be a catch (...) block somewhere on the stack that can deal with any exception.

 

Noexcept specifiers

 

4. Run-Time Type Information (RTTI)

 

RTTI refers to the ability of the system to report on the dynamic type of an object and to provide information about that type at runtime (as opposed to at compile time), when utilized consistently can be a powerful tool to ease the work of the programmer in managing resources. dynamic_cast

 

Consider what you have already learned about the dynamic_cast keyword and let's say that we have the following class hierarchy:

 

class Interface

{

public:

virtual void GenericOp() = 0;// pure virtual function

};

 

class SpecificClass : public Interface

{

public:

 

virtual void GenericOp(); virtual void SpecificOp();

};

 

Let's say that we also have a pointer of type Interface*, like so: Interface* ptr_interface;

 

Supposing that a situation emerges that we are forced to presume but have no guarantee that the pointer points to an object of type SpecificClass and we would like to call the member SpecificOp() of that class. To dynamically convert to a derived type we can use dynamic_cast, like so:

 

SpecificClass* ptr_specific = dynamic_cast<SpecificClass*>(ptr_interface); if( ptr_specific ){

 

// our suspicions are confirmed -- it really was a SpecificClass ptr_specific->SpecificOp();

 

}else{

//  our suspicions were incorrect -- it is definitely not a SpecificClass.

 

//  The ptr_interface points to an instance of some other child class of the base InterfaceClass. ptr_interface->GenericOp();

 

};

 

 

With dynamic_cast, the program converts the base class pointer to a derived class pointer and allows the derived class members to be called. Be very careful, however: if the pointer that you are trying to cast is not of the correct type, then dynamic_cast will return a null pointer.

 

We can also use dynamic_cast with references.

SpecificClass& ref_specific = dynamic_cast<SpecificClass&>(ref_interface);

 

This works almost in the same way as pointers. However, if the real type of the object being cast is not correct then dynamic_cast will not return null (there's no such thing as a null reference). Instead, it will throw a std::bad_cast exception.

 

typeid Syntax

typeid( object );

 

The typeid operator, used to determine the class of an object at runtime. It returns a reference to a std::type_info object, which exists until the end of the program, that describes the "object". If the "object" is a dereferenced null pointer, then the operation will throw a std::bad_typeid exception. Objects of class std::bad_typeid are derived from std::exception, and thrown by typeid and others.

 

The use of typeid is often preferred over dynamic_cast<class_type> in situations where just the class information is needed, because typeid, applied on a type or non de-referenced value is a constant-time procedure, whereas dynamic_cast must traverse the class derivation lattice of its argument at runtime. However, you should never rely on the exact content, like for example returned by std::type_info::name(), as this is implementation specific with respect to the compile. It is generally only useful to use typeid on the dereference of a pointer or reference (i.e. typeid(*ptr) or typeid(ref)) to an object of polymorphic class type (a class with at least one virtual member function). This is because these are the only expressions that are associated with run-time type information. The type of any other expression is statically known at compile time.

 

Example

#include <iostream>

#include <typeinfo> //for 'typeid' to work

 

class Person { public:

 

// ... Person members ...

 

virtual ~Person() {} };

 

class Employee : public Person { // ... Employee members ...

 

};

 

int main () { Person person;

 

Employee employee; Person *ptr = &employee;

// The string returned by typeid::name is implementation-defined

std::cout << typeid(person).name() << std::endl;        // Person (statically known at compile-time)

 

 

std::cout << typeid(employee).name() << std::endl; // Employee (statically known at compile-

time)

std::cout << typeid(ptr).name() << std::endl;     // Person * (statically known at compile-time)

std::cout << typeid(*ptr).name() << std::endl;   // Employee (looked up dynamically at run-

time

//                      because it is the dereference of a

//                      pointer to a polymorphic class)

}

 

Output (exact output varies by system): Person

 

Employee Person* Employee

 

 

In RTTI it is used in this setup:

const std::type_info& info = typeid(object_expression);

 

Sometimes we need to know the exact type of an object. The typeid operator returns a reference to a standard class std::type_info that contains information about the type. This class provides some useful members including the == and != operators. The most interesting method is probably:

 

const char* std::type_info::name() const;

 

This member function returns a pointer to a C-style string with the name of the object type. For example, using the classes from our earlier example:

 

const std::type_info &info = typeid(*ptr_interface); std::cout << info.name() << std::endl;

This program would print something like SpecificClass because that is the dynamic type of the pointer ptr_interface.

 

typeid is actually an operator rather than a function, as it can also act on types: const std::type_info& info = typeid(type);

 

for example (and somewhat circularly)

const std::type_info& info = typeid(std::type_info);

 

will give a type_info object which describes type_info objects. This latter use is not RTTI, but rather CTTI (compile-time type identification).

 

Limitations

 

There are some limitations to RTTI. First, RTTI can only be used with polymorphic types. That means that your classes must have at least one virtual function, either directly or through inheritance. Second, because of the additional information required to store types some compilers require a special switch to enable RTTI.

 

Note that references to pointers will not work under RTTI: void example( int*& refptrTest )

 

{

std::cout << "What type is *&refptrTest : " << typeid( refptrTest ).name() << std::endl;

}

 

Will report int*, as typeid() does not support reference types. Misuses of RTTI

 

RTTI should only be used sparingly in C++ programs. There are several reasons for this. Most importantly, other language mechanisms such as polymorphism and templates are almost always superior to RTTI. As with everything, there are exceptions, but the usual rule concerning RTTI is more or less the same as with goto statements. Do not use it as a shortcut around proper, more robust design. Only use RTTI if you have a very good reason to do so and only use it if you know what you are doing.


Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail
Object Oriented Programming(OOP) : Advanced Programming : Exception handling - C++ |


Privacy Policy, Terms and Conditions, DMCA Policy and Compliant

Copyright © 2018-2024 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.