0% found this document useful (0 votes)
21 views34 pages

COMP2006 Lecture 14 Exceptions and RAII

This lecture covers exceptions in C++ and RAII (Resource Acquisition Is Initialization). It discusses three ways to handle errors: returning error values, setting global error codes, and throwing exceptions. Exceptions allow error values of any type to be returned. Exceptions are thrown and caught using try/catch blocks. Catch clauses match the type of exception thrown, and subclasses are caught by their parent classes. The order of catch clauses matters. Pointers and objects are treated differently for catching exceptions.

Uploaded by

zombiten3
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views34 pages

COMP2006 Lecture 14 Exceptions and RAII

This lecture covers exceptions in C++ and RAII (Resource Acquisition Is Initialization). It discusses three ways to handle errors: returning error values, setting global error codes, and throwing exceptions. Exceptions allow error values of any type to be returned. Exceptions are thrown and caught using try/catch blocks. Catch clauses match the type of exception thrown, and subclasses are caught by their parent classes. The order of catch clauses matters. Pointers and objects are treated differently for catching exceptions.

Uploaded by

zombiten3
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 34

COMP2006

C++ Programming
Lecture 14

Dr Chao Chen

1
This lecture: exceptions and RAII
How could we report errors?
1. Return an error value from function
– Remember to check return value on each call
– Must have a valid ‘error’ return value
– How do we propagate the error? (return again?)

2. Set a global error code


– Again, have to remember to check it after each call

3. Throw an exception (to report error)


– Requires an exception handling mechanism
– This lecture
2
– Similar to Java, but important differences
Exceptions
• Exceptions are ‘thrown’ to report exceptional circumstances
– Similar to being able to return an error value of whatever type is
desired
• You can throw any type of object, fundamental type or pointer as
an exception in C++
– These are different things (pointers != objects)
– The standard class library provides some standard exception
types, all derived from std::exception class
– It is good practice to throw objects which are of sub-classes of
std::exception rather than arbitrary types
• You add handler code to catch the exception
• Stack Unwinding -- one function at a time (as if the functions
returnedunwound immediately) until a catch which matches the
type thrown is found
– Like returning from the functions, with same problems
– Stack objects are destroyed, heap objects are not!
3
Catching exceptions – very like Java
• First specify that you want to check for exceptions (try)
• Then call the code which may raise the exceptions
• Then specify which exceptions you will catch, and what
to do (this can include re-throwing them)
– Use throw without arguments in a catch clause to rethrow them
try
{ Assume that foo() throws an exception
foo(); e.g. throw 1;
} or MyException ob; throw ob;
catch ( int& i )
{
cout << "int was thrown by foo()" << endl;
}
catch ( … )
{
cout << "Any other exception was thrown" << endl;
} 4
The catch clause
• A catch clause will match an exception
of the specified type
• catch clauses are checked in the order in
which they are encountered
– The order of the catch clauses matters!
• Pointers and objects are different
• Exceptions are thrown by value
– Catch by reference or by value would work
– Catch by reference avoids the copy (and slicing!!!)
• chars, shorts, ints (etc) are different things
• catch ( ... ) will match ANY exception
5
Exception thrown by new
void foo() Loop forever – until it fails
{
while ( true )
{
new int[10000]; If memory allocation fails,
cout << '.'; an exception of type
} bad_alloc is thrown
}
Catching this exception potentially
int main() allows handling the out of memory
{ problem, e.g. ‘save and exit’
try
{ foo(); }
catch ( bad_alloc )
{ cout << "bad_alloc exception thrown" << endl; }
catch ( ... )
{ cout << “Other exception thrown " << endl; }
6
}
Multiple functions
#include <iostream> void foo()
using namespace std; {
void bar2() try { bar(); }
{ catch( const char* sz )
switch( rand() % 3 ) {
{ cout << "char*" << endl;
case 0: throw 1.2f; }
case 1: throw "string"; }
case 2: throw new
string("String"); int main()
} {
} bar2() throws
for ( int i=0 ; i<20 ; i++ )
an exception of
{
void bar() a random type
try { foo(); }
{ catch( ... )
try { bar2(); } {
catch( float f ) cout << "Other" << endl;
{ }
cout << "float" << endl; }
} } 7
}
The catch clause and sub-classes
• Sub-class objects ARE base class objects
– Because inheritance models the ‘is-a’ relationship
– catch clauses will match sub-class objects
– e.g.:
catch ( BaseClass& b ) { }
will also catch sub-class objects
catch ( BaseClass* b ) { }
will also catch sub-class pointers
• Reminder: Pointers and objects are different
– First catch will NOT catch thrown pointers
– Second catch will NOT catch thrown objects 8
Catching Base class objects
struct Base int test1()
{ {
virtual void temp() {} try
}; {
Base b;
struct Sub1 : public Base throw b;
{ }
void temp() {}
}; catch ( Sub1& b )
A { cout << "Sub1" << endl; }
struct Sub2 : public Base
{ catch ( Base& b )
void temp() {}
B { cout << “Base" << endl; }
};
catch ( Sub2& b )
C { cout << "Sub2" << endl; }
Base class object is thrown
catch ( ... )
Which catch clause will be used? D { cout << "Other" << endl; }
} 9
Answer
• B

• Check catches in order:


– It is NOT a Sub1
– It is a Base

10
Catching Sub1 class objects
struct Base int test1()
{ {
virtual void temp() {} try
}; {
Sub1 s1;
struct Sub1 : public Base throw s1;
{ }
void temp() {}
}; catch ( Sub1& b )
A { cout << "Sub1" << endl; }
struct Sub2 : public Base
{ catch ( Base& b )
void temp() {}
B { cout << “Base" << endl; }
};
catch ( Sub2& b )
C { cout << "Sub2" << endl; }
Sub-class Sub1 object is thrown
catch ( ... )
Which catch clause will be used? D { cout << "Other" << endl; }
} 11
Answer
• A
• Check catches in order:
– It is a Sub1

12
Catching Sub2 class objects
struct Base int test1()
{ {
virtual void temp() {} try
}; {
Sub2 s2;
struct Sub1 : public Base throw s2;
{ }
void temp() {}
}; catch ( Sub1& b )
A { cout << "Sub1" << endl; }
struct Sub2 : public Base
{ catch ( Base& b )
void temp() {}
B { cout << “Base" << endl; }
};
catch ( Sub2& b )
C { cout << "Sub2" << endl; }
Sub-class Sub2 object is thrown
catch ( ... )
Which catch clause will be used? D { cout << "Other" << endl; }
} 13
Answer
• B
• Check catches in order:
– It is not a Sub1
– It is a Base (Sub2 objects are Base objects)
• Note: The order here matters
– It gets caught by the Base catch before it gets
to the Sub2 catch
– The compiler may give you a warning here
about the sub-class type exception being
caught by the base class catch
– gcc / g++ will
14
Catching Sub2 class pointer
struct Base int test1()
{ {
virtual void temp() {} try
}; {
Sub2* ps2 = new Sub2;
struct Sub1 : public Base throw ps2;
{ }
void temp() {}
}; catch ( Sub1& b )
A { cout << "Sub1" << endl; }
struct Sub2 : public Base
{ catch ( Base& b )
void temp() {}
B { cout << “Base" << endl; }
};
catch ( Sub2& b )
C { cout << "Sub2" << endl; }
Sub-class Sub2 object is thrown
catch ( ... )
Which catch clause will be used? D { cout << "Other" << endl; }
} 15
Answer
• D
• Check catches in order:
– It is not a Sub1
– It is not a Base
– It is not a Sub2
– … catches all exceptions
• Pointers are not objects
• Objects are not pointers
• Note: References and objects will match
– & Just says whether a copy is made or not
16
Exceptions summary
and final comments

17
Aside: exceptions and throw()
• throw() at the end of the function declaration
limits the exceptions which can be thrown
– It is optional
– In Java, throws <types> is mandatory
• If specified, then all exception types which can
be thrown by the function must be specified
– Throwing a different type will terminate the program
• Examples:
void MyFunction(int i) throw();
• Function will not throw exceptions (noexcept).
void MyFunction(int i) throw(int);
• Function will only throw ints as exceptions
void MyFunction(int i) throw(...);
• Function could throw ANY exception 18
Other Exception Comments
• The destructor is guaranteed to be called for a stack
object when the stack frame is destroyed
– It is the only function which we can guarantee will be called when
an exception occurs
– Make sure that you free any heap memory first

1. Throwing an exception while there is an uncaught


exception will end the program
– Ensure that exceptions cannot be thrown from within a
destructor because the destructor could be called as a result of
an exception, e.g. to destroy objects on the stack
2. Not catching a thrown exception will end the program

19
Exceptions Advice
• Try to catch (and handle) an exception as close as
possible to the place it was generated
• Do not catch an exception if you cannot do
something with it (leave it to your caller)
• If you throw exceptions, prefer to throw standard
class library exceptions, or sub-classes of these
– Choose meaningful exception (e.g. std::invalid_argument)
• My suggestion – and ONLY a suggestion:
– There is a risk involved in using exceptions – i.e. less
control over the flow of control, like an implicit return,
so, use exceptions only for exceptional circumstances
• Note: C++ has no ‘finally’ clause – it uses RAII… 20
RAII

Resource Acquisition Is Initialization

21
Any issue with this function?
void foo()
{ Allocate memory
int* iarray = new int[100];
for (int i=0;i<100;i++)
{
iarray[i] = rand(); Set each element to a random value

if ( (iarray[i]%5) == 0 )
{ End function if random
cout << "end " << i; number gives specific values
return;
}

cout << iarray[i] << " ";


}
delete [] iarray; Free memory
}
22
Note: This example has nothing directly to do with exceptions/exception handling, yet
Problem : resource/memory leaks
// Function which may throw an exception
void bar() Note: no ‘throws’/ ‘throw’ on the function.
{ If you add a throw() on a function then you are
throw 1; guaranteeing that it ONLY throws those exceptions
} (throw any others and program ends).
But we have no warning that it may throw exception!
// This function throws an exception so the
// objects are not destroyed
void foo()
bar() throwing an
{
exception will mean
// Create objects delete is not called
MyClass* pOb1 = new MyClass; for pOb

// Call function which may throw an exception


bar(); Function ends before here
The delete never gets called
delete pOb1; Objects not deleted
} Memory not freed 23
Similar issues with resources not released
void Version1()
{
FILE* f = fopen( "out1.txt", "w" );
fprintf( f, "Output text" );

// Do something which throws exception or returns?


throw 1;

// Never gets to the close, so file possibly


// not flushed until process ends
printf("Closing file manually\n" );
fclose(f);
}
In Java we may put a ‘finally’ clause in for the close, to ensure that the code to
close the file is always called, regardless of how the function exits. This is more tricky
in C++ than Java because we don’t know what will throw an exception 24
RAII : Resource Acquisition
Is Initialisation
A useful concept to understand

25
When a function ends…
• Remember back to the discussion of the stack…
– When a function ends, its stack frame is removed
– ALL stack objects (local variables) are destroyed
• Destructors are called for each
– This applies even if the function is ended due to an exception!
• RAII takes advantage of this
– My opinion (only mine?): may be better named in this case:
“Resource Release On Object Destruction” (RROOD?)
• On initialisation, get the resource
• On destruction, release the resource
• The main idea of RAII:
– Create stack object to ‘wrap’ the thing you need to release
– When stack object is destroyed, the thing gets released (e.g. file closed)

26
Simplest(?) file ‘wrapper’ class
class Wrapper void version2a()
{ {
public: Wrapper w;
FILE* pFile; w.pFile = fopen(
"out2a.txt", "w" );
// No constructor -
default created fprintf( w.pFile,
"Output text" );
// Key part is the
destructor! // Do something which could
~Wrapper() // throw exception
{ throw 1;
fclose(pFile);
} // Never gets to fclose
}; // but we don't care now
// No need for:
// fclose(w.pFile);
} 27
Class definition Code to use it
A better wrapper class
class MyFile // Is file open?
{ bool isopen()
FILE* pFile; { return pFile != NULL;
}
Object can say whether file is open
public:
// Constructor // Close file if open
MyFile( void close()
const char* szFileName, {
const char* szType = "r" ) if ( pFile != NULL )
: pFile(NULL) fclose( pFile );
{ pFile = NULL;
pFile = fopen( } And allow you to close it
szFileName, szType );
} // Destructor!!!
~MyFile()
// Conversion operator!!! {
operator FILE*() close();
{ return pFile; } } 28
Use object anywhere a FILE* is needed };
Using the wrapper
void version2b() Reminder:
{ class MyFile
// FILE* f = fopen("out2.txt","w"); {
MyFile file( "out2.txt", "w" ); public:
MyFile( ... )
Create object – attempts to open file
{ /* fopen() */ }

fprintf( file, "Output text" ); operator FILE*()


Use object as a FILE* { return pFile; }

// Do something which throws void close()


// exception or returns? { /* if open, then
throw 1; Stack object gets destroyed fclose() it */ }

// Never gets to the close below ~MyFile()


// but no problem { close(); }
file.close(); };
} 29
An unnecessary close(), see…
Wrapping pointers

30
Wrapping pointers/arrays : int*
class Deleter class ArrayDeleter
{ {
public: public:
int* pOb; // wrapped ptr int* pArray;

// construct from pointer // construct from pointer


Deleter(int *pOb = NULL) ArrayDeleter(
: pOb(pOb) int* pArray = NULL)
{ } : pArray(pArray)
{ }
// destroy the object
~Deleter() // destroy the array
{ ~ArrayDeleter()
if ( pOb ) {
delete pOb; if ( pArray )
} delete [] pArray;
}; }
Wraps up the pointer Need to use delete [] on array!
};
Deletes the object pointed at
31
when stack object is destroyed
Wrapping pointers : templates
template<class T> template<class T>
class Deleter class ArrayDeleter
{ {
public: public:
T* pOb; // wrapped pointer T* pArray;

// construct from pointer // construct from pointer


Deleter(T *pOb = NULL) ArrayDeleter(
: pOb(pOb) T* pArray = NULL)
{ } : pArray(pArray)
{ }
// destroy the object
~Deleter() // destroy the array
{ ~ArrayDeleter()
if ( pOb ) {
delete pOb; if ( pArray )
} delete [] pArray;
}; }
Wraps any specific Wraps any specific
}; 32
type of pointer type of array
Fixing the earlier problem…
void foo() // Creates object
{
MyClass* pOb = new MyClass;

Deleter<MyClass> del(pOb);

bar(); // bar throws exception


//delete pOb; // No longer needed
} 33
Next lecture
• Smarter pointers – extending the wrappers
– Objects which act like pointers
• Smart pointers
– Standard class library classes for this
• How to use them, and what NOT to do

34

You might also like