l7: exceptions problem description error handling –terminate the program –return a value...
TRANSCRIPT
L7: Exceptions• Problem Description
• Error Handling
– terminate the program
– return a value representing an error
– return a legal value and leave the program in an illegal state
– call an error-handling routine
– use exceptions
• Use inheritance to implement exceptions
• Resource Management
• Typeid
• Chapter 8 and 14 of Stroustrup
Exceptions
• See Chapter 8 and 14 of Stroustrup• Library or class can detect run-time errors,
but may not know what to do with them.• Caller may know what to do with run-time
error, but doesn't know how to detect them.• What can be done?
Error Handling
• Possibilities include:– terminate the program
– return a value representing an error
– return a legal value and leave the program in an illegal state
– call an error-handling routine
– use exceptions
Terminate Program
• Default behaviour for uncaught exceptions in C++
• Not appropriate for libraries
Error Value Return
• Often no plausible error value• Inconvenient because error must be checked
for every function call
Illegal State
• Behaviour of C library functions• Global variable “errno” is set by library. • Calling routine must explicitly check errno.
#include <cstdio>#include <cstdlib>#include <errno.h>
int main() { //if error, fopen returns NULL and sets errno FILE * f = fopen("filename", "r");
if (!f) // an error has occurred, check errno {
// file doesn't exist if (errno==ENOENT) printf("No file\n");
// perror checks errno and prints message perror("An error occurred opening file"); exit(-1); }}
Error Handling Routine
• Exceptions not meant to handle this case• But error handling routine has to choose a
mechanism for dealing with errors
#include <csignal>#include <cstdio>#include <cstdlib>
void sighandler(int sig){ printf("received signal %d\n", sig); exit(-1);}
int main(){ // register the handler with a seg fault signal(SIGSEGV, sighandler);
// do something to cause a seg fault delete (int*)-10;}
Exceptions
• Designed to support handling of errors and other exceptional conditions
• Non-local control structure based on stack unwinding
• An alternative to the usual return mechanism• Some legitimate uses that are not related to
errors
Stack Unwinding
When an exception is thrown and control passes from a try block onto a handler, the C++ run time calls the destructors of all the automatic objects constructed since the beginning of the try block. This process is called stack unwinding.
http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp
Exceptions
• Exceptional does not necessarily mean– “almost never happens”, or
– “a disastrous event”.• Can be thought of as:
– “some part of the system couldn't do what it was asked to do”
• So, don't use exceptions when errors can be handled locally.
Throw
struct RangeError {
int i;
RangeError(int ii){i=ii;};
};
char to_char(int i) {
if (i < numeric_limits<char>::min() || i > numeric_limits<char>::max())throw RangeError(i);
return (char)i;
}
Catch
void g(int i) {
try {char c = to_char(i);// ...
} catch (RangeError r) {cerr << “can't convert “ << r.i << “ to char\n”;
}
}
Inheritance and Exceptions
class Matherr {};
class Overflow : public Matherr {};
class Underflow: public Matherr {};
class Zerodivide: public Matherr {};
void f() {
try { // ... }
catch (Overflow) {// handle overflow};
catch (Matherr) {// handle any other Matherr};
catch (...) {// handle any other exception};
}
Resource Management
void use_file(const char *fn) {
File *f = fopen(fn, “r”);
try {// use f
} catch (...) {fclose(f);throw;
}
fclose(f);
}
Resource Allocation is Initialisation
class FilePtr {
private: FILE *p;
public:FilePtr(const char *name, const char *a){p = fopen(name, a);};~FilePtr(){if (p) fclose(p);};
};
Resource Allocation is Initialisation
void use_file(const char *p) {
FilePtr f(p, “r”);
try {// use f
} catch (...) {// handle exception
}
}
typeid Given an object (or a variable), typeid
operator retrieves the type of an object (or a variable) to which a pointer or a reference points.
Can be used to discover things about the actual type (usually its name).
An alternative to dynamic_cast.
Inheritanceclass Shape { public: virtual void draw() = 0;};class Rectangle: public Shape { public: void draw() { cout << "draw a rectangle\n";};
};class Circle : public Shape { public: void draw() { cout << "draw a circle\n"; };
};
Example 1: An Alternative to Downcasting
void rotate(const Shape &shape) {
if (typeid(shape)==typeid(Circle)) {
cout << "rotate Circle\n";
} else if (typeid(shape)==typeid(Rectangle)) {
cout << "rotate rectangle\n";
}
}
Circle c;
Rectangle r;
rotate(c); // rotate Circle
rotate(r); // rotate Rectangle
In the case of a polymorphic class (containing pure virtual functions), the typeid operator retrieves the most derived type of this object, if the argument of the typeid is an object (pointed by a pointer).
If the argument is a pointer itself, the typeid operator regards the type of this object as the pointer of this class.
Example 2
void f(Shape &circle, Shape *rectangle) {
if(typeid(circle) == typeid(Circle))
cout << "a is of type Circle\n";
if(typeid(*rectangle) == typeid(Rectangle))
cout << "a is of type Rectangle\n";
Shape *s;
if(typeid(rectangle) == typeid(s))
cout << "a is of type Shape\n";
}
int main() {
Circle c;
Rectangle r;
f( c, &r );
}
Example 2
Example 3
In the case of a non-polymorphic class, the typeid operator retrieves the type of the object given.
class C {
public:
C() {};
};
class D : public C {
public:
D() {};
};
Example 3
D d;
C *c1 = &d;
C &c2 = d;
cout << "c1: " << typeid(*c1).name() << endl; // C
cout << "c2: " << typeid(c2).name() << endl; // C