OOP C12 Exceptions

Intro

Runtime Error

The basic philosophy of C++ is

Badly formed codes will not be run.

  • There’s always something happening in run-time and
  • It is very important to deal with all possible situation in the future running.

Example: Read files

Briefly reading a file means

  • open the file;
  • determine its size;
  • allocate that much memory;
  • read the file into memory;
  • close the file;

There maybe many kinds of errors during the whole process. If we mark every kind of error an error code, the code may be complicated.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
errorCodeType readFile{ 
initialize errorCode = 0;
open the file;
if ( theFilesOpen ){
determine its size;
if ( gotTheFileLength ){
allocate that much memory;
if ( gotEnoughMemory ){
read the file into memory;
if ( readFailed ){
errorCode = -1;
}
else{
errorCode = -2;
}
}
else{
errorCode = -3;
}
}
close the file;
if ( theFILEDidntClose && errorCode == 0 ){
errorCode = -4;
}
else{
errorCode = errorCode and -4;
}
else{
errorCode = -5;
}
return errorCode;
}

With exception,

1
2
3
4
5
6
7
8
9
10
11
12
try{ 
open the file;
determine its size;
allocate that much memory;
read the file into memory;
close the file;
}
catch ( fileOpenFailed ) { doSomething;}
catch ( sizeDeterminationFailed ) { doSomething;}
catch ( memoryAllocationFailed ) { doSomething;}
catch ( readFailed ) { doSomething;}
catch ( fileCloseFailed ) { doSomething;}

At the point where the problem occurs, you

  • might not know what to do with it;
  • but do know that you must stop. And somebody, somewhere, must figure out what to do.

exception clean up error handling code: separates

  • the codes that describes what you want to do
  • from the codes that is executed.

Example: Invalid Index

In vector,

1
2
3
4
5
6
7
8
9
10
11
template <class T> class Vector { 
private:
T* m_elements;
int m_size;
public:
Vector (int size = 0):m_size(size) ...
~Vector (){ delete [] m_elements; }
void length(int);
int length() { return m_size; }
T& operator[](int);
};

What should the [] operator do if the index is not valid?

method notes
1 Return random memory object may be invalid
2 Return a special error value for code like x = v[2] + v[4], the operator will continue to fail
3 Just exit
4 Die gracefully (with autopsy!) like assert

Raise an exception

Class to represent the error

1
2
3
4
5
6
7
8
class VectorIndexError { 
public:
VectorIndexError(int v) : m_badValue(v) { }
~VectorIndexError() { }
void diagnostic() { cerr << "index " << m_ badValue << "out of range!"; }
private:
int m_badValue;
};

Then the error could be thrown to mark the error.

1
2
3
4
5
6
7
8
9
template <class T> 
T& Vector<T>::operator[](int indx) {
if (indx < 0 || indx >= m_size) {
// VectorIndexError e(indx);
// throw e; throw
VectorIndexError(indx);
}
return m_elements[indx];
}

Caller Handling levels

  • Don’t Care: If code never even suspects a problem, then code never gets there.

    1
    2
    3
    4
    5
    6
    7
    int func() { 
    Vector<int> v(12);
    v[3] = 5;
    int i = v[42]; // out of range and die
    // control never gets here!
    return i * 5;
    }
  • Mildly interested: warning but let the exception propagate.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void outer2() { 
    String err("exception caught");
    try {
    func();
    } catch (VectorIndexError) {
    cout << err;
    throw; // propagate the exception(re-raise)
    }
    }
  • Cares Deeply: use try in the outer caller to func:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void outer() { 
    try {
    func();
    func2();
    }
    catch (VectorIndexError& e) {
    e.diagnostic(); // This exception does not propagate
    }
    cout << "Control is here after exception";
    }
  • Doesn’t care about the particulars

    1
    2
    3
    4
    5
    6
    7
    void outer3() { 
    try {
    outer2();
    } catch (...) { // ... catches ALL exceptions!
    cout << "The exception stops here!";
    }
    }

throw statement raises the exception

  • Control propagates back to first handler for that exception (following the call chain)
  • Objects on stack are properly destroyed

throw exp; throws value for matching, while throws;(valid only within a handler) re-raises the exception being handled.

Try Blocks

the try block takes form of

1
2
3
try { ... } 
catch ... //handler
catch ...
  • Any number of handlers accepted.
  • No Handlers, No try block.
  • It Costs cycles.

Exception handlers

A handler, taking a single argument (like a formal parameter)

  • Selects exception by type
  • could re-raise exceptions
1
2
3
4
5
6
catch (SomeType v) { 
// handler code
}
catch (...) {
// handler code
}

Handlers’ Checking

Handlers are checked in order of appearance.

  1. Check for exact match
  2. Apply base class conversions: Reference and pointer types, only
  3. Ellipsis (…) match all like catch(...)

Inheritance can be used to structure exceptions!

1
2
3
4
5
6
class MathErr { ...
virtual void diagnostic();
};
class OverflowErr : public MathErr { ... }
class UnderflowErr : public MathErr { ... }
class ZeroDivideErr : public MathErr { ... }

The code is

1
2
3
4
5
6
7
8
9
10
11
12
13
try { 
// code to exercise math options
throw UnderFlowErr();
}
catch (ZeroDivideErr& e) {
// handle zero divide case
}
catch (MathErr& e) {
// handle other math errors
}
catch (...) {
// any other exceptions
}

Standard library exceptions

new raises a bad_alloc() exception when failing.

1
2
3
4
5
6
7
8
void func() { 
try {
while(1) {
char *p = new char[10000];
}
}
catch (bad_alloc& e) { }
}

standard

Exception specifications

Part of function prototypes.

1
2
3
void abc(int a) : throw(MathErr) { 
...
}

Declare which exceptions function might raise. A function with no Exception specifications may throw any type of exception.

  • Exceptions are Not checked at compile time.
  • If an exception not in the list propagates out at run time, the unexpected exception is raised.

More exceptions

Exceptions and constructors

For failures in constructor:

  • No return value is possible
  • Use an uninitialized flag
  • Defer work to an Init() function

Two stages construction strategy

  1. Do normal work in constructor. Initialize
    • all member objects
    • all primitive members
    • all pointers to 0
  2. Do addition initialization work in Init() function: Those initializations requesting
    • File
    • Network connection
    • Memory
    • …(any resource)

If the constructor can’t complete, better throw an exception.

  • Destructors for those objects whose constructor didn’t complete won’t be called.
  • Therefore Cleaning up allocated resources before throwing is neccesary.

Exceptions and destructors

Destructors are called when

  • Normal call: object exits from scope
  • During exceptions: stack unwinding invokes dtors on objects as scope is exited.

Design and usage with exceptions

Handlers


OOP C12 Exceptions
http://example.com/2023/06/05/OOP-12/
Author
Tekhne Chen
Posted on
June 5, 2023
Licensed under