Derived Exception-Type
Derived exception-types let exception types inherit from each other, just like class inheritance β providing polymorphism across exception types.
Why do we want an exception hierarchy?
Because different callers want different degrees of specificity. Low-level I/O code knows itβs a
Fileerror; a high-level retry loop only cares that someIOerror happened. Inheritance lets the same raise be caught at any level of the hierarchy, so higher-level code can couple toIOrather thanFileand not break when the underlying implementation changes.
Hierarchy shape (from the notes)
Exception
/ \
IO Arithmetic
/ \ / | \
File Network DivByZero Overflow Underflow
Matching rules
- A handler for a base type catches any derived type.
- Most propagation mechanisms do a linear search of the handlers in a guarded block β so order matters:
try { ... }
catch( Arithmetic & ) { ... }
catch( Overflow ) { ... } // NEVER selected β Arithmetic catches it first- Higher-level code should catch general types to reduce tight coupling to specific implementations (tight coupling forces churn in higher-level code when low-level code changes).
Catch by reference when subclassing
struct B {};
struct D : public B {};
try {
throw D();
} catch( B e ) { // TRUNCATION β e is a B now, cannot down-cast
...
}
try {
throw D();
} catch( B & e ) { // no truncation β can dynamic_cast<D>(e)
... dynamic_cast<D&>(e) ...
}By value, the exception is truncated from its dynamic type to the static type at the handler. By reference, the dynamic type is preserved and down-casting works.
uC++ doesn't truncate on raise
Catching truncation (above) is different from raising truncation. In uC++,
_Throwraises the original object of type D even when a function parameter was declared as base B β so handlers can catch the specific derived type, not the base. This is the_Throw trow in the EHM table.