We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Errors step 2 of 4
The catch that threw half of it away
Exceptions are for the failures a caller cannot reasonably be asked to check
on every call: the disk filled, the socket died, the invariant a constructor
could not establish. They are not for “the key was not in the map”, which is
an ordinary Tuesday and belongs in an optional.
What they cost
Modern implementations use table-driven unwinding. Entering a try block
costs nothing at runtime — no register saved, no branch — and the normal path
through a function that might throw is as fast as one that cannot. The cost
lives entirely in the throw: allocating the exception, walking the stack,
consulting tables, running destructors on the way. That is somewhere between
hundreds and thousands of times the cost of returning a value.
Which gives the rule directly:
Free when it does not happen, ruinous when it does. So it must not happen often. An exception in a loop is a design error, not a slow path.
Throw by value, catch by const reference
throw NotFound{}; // yes
throw new NotFound{}; // no: who deletes it?
catch (const AppError& e) // yes
catch (AppError e) // no
catch (...) // only to log and rethrow
The middle one is what this problem is about. Catching by value copies the thrown object into a variable of the base type, and a copy of a base cannot hold a derived object. Everything the derived class added is gone, and the virtual functions dispatch to the base’s versions. The object was correct when it was thrown; it was truncated on arrival.
This is object slicing, and it is not specific to exceptions — it happens wherever a derived object is assigned to a base by value. Catch handlers are simply where it does the most damage, because the whole point of the hierarchy was to let the handler ask what went wrong.
throw NotFound{} -> code() == 404
catch (AppError e) -> e is an AppError. code() == 500.
catch (const AppError& e) -> e refers to the NotFound. code() == 404.
The reference binds to the actual object and changes nothing about it. It is also cheaper, which is the least interesting reason to prefer it.
Ordering and rethrow
Handlers are tried in source order, not by best match, so the most
derived types go first — a catch (const AppError&) above a
catch (const NotFound&) makes the second one dead code. Inside a handler,
a bare throw; rethrows the original exception, preserving its dynamic type;
throw e; throws a copy of the sliced e, which is the same bug again.
Your task
int handle(int request);
fetch is given. It returns a value, or throws NotFound (code 404) or
Denied (code 403), both deriving from AppError (code 500).
Return the value on success, and the exception’s code on failure. Do not
change fetch or the exception classes.
The starter catches and returns a code. Every failure comes back as 500.
Stuck?
C++ reference solution
Sign in to attempt this problem and reveal the reference solution.