Show Lecture.Exceptions as a slide show.
CS253 Exceptions
Basic Syntax
-
try { β¦ }
-
Execute some code, which might
throw
an exception.
-
throw
value;
-
Throw an exception. At that point, program execution transfers
to any applicable
catch
clause. If no catch
clause applies,
then the program dies.
-
catch(
specification) { β¦ }
-
Deal with an exception.
specification is very much like a function argument declaration.
Normal Operation
void snap() {
cout << "mind\n";
throw "Thanos";
cout << "power\n"; // πππ
}
int main() {
cout << "reality\n";
try {
cout << "soul\n";
snap();
cout << "space\n"; // πππ
}
catch (const char *who) {
cout << "Caught " << who << '\n';
}
cout << "time\n";
}
reality
soul
mind
Caught Thanos
time
throw
without catch
If something is thrown but never caught, then the special function
terminate()
is called, which complains in an implementation-defined manner,
and stops the program by calling abort()
.
throw "ouch";
terminate called after throwing an instance of 'char const*'
SIGABRT: Aborted
A Special Case
For g++
, an uncaught standard exception
(derived from std::exception
) gets its .what()
string displayed.
throw overflow_error("It done overflowed!");
terminate called after throwing an instance of 'std::overflow_error'
what(): It done overflowed!
SIGABRT: Aborted
I donβt believe that this behavior is mandated by the standard,
but it sure makes it easier to debug an uncaught exception.
Objects get destroyed appropriately
#include "Loud.h"
Loud a('a');
void foo() {
Loud b('b');
Loud c('c');
}
int main() {
Loud d('d');
foo();
Loud e('e');
return 0;
}
Loud::Loud() [c='a']
Loud::Loud() [c='d']
Loud::Loud() [c='b']
Loud::Loud() [c='c']
Loud::~Loud() [c='c']
Loud::~Loud() [c='b']
Loud::Loud() [c='e']
Loud::~Loud() [c='e']
Loud::~Loud() [c='d']
Loud::~Loud() [c='a']
throw
without catch
#include "Loud.h"
Loud a('a');
void foo() {
Loud b('b');
throw 42;
Loud c('c');
}
int main() {
Loud d('d');
foo();
Loud e('e');
return 0;
}
Loud::Loud() [c='a']
Loud::Loud() [c='d']
Loud::Loud() [c='b']
terminate called after throwing an instance of 'int'
SIGABRT: Aborted
You can throw
anythingβdoesnβt have to be a special type or object.
I mean anything
Reallyβyou can throw
and catch
any type. For example:
throw 42;
terminate called after throwing an instance of 'int'
SIGABRT: Aborted
throw "alpha";
terminate called after throwing an instance of 'char const*'
SIGABRT: Aborted
throw "beta"s;
terminate called after throwing an instance of 'std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >'
SIGABRT: Aborted
I mean anything
int n=42; throw "Value no good: " + to_string(n);
terminate called after throwing an instance of 'std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >'
SIGABRT: Aborted
throw logic_error("That failed miserably");
terminate called after throwing an instance of 'std::logic_error'
what(): That failed miserably
SIGABRT: Aborted
Itβs nice that the what()
value for a std::exception
gets displayed.
I donβt know if thatβs standard.
Standard Exceptions
There are a number of classes defined by the C++ standard,
in <
stdexcept>
. Itβs best to use them,
or classes derived from them, as opposed to rolling your own.
Standard Exception Class Hierarchy
βββββββββββββββββββββββββββ
β exception β
βββββββββββββββββββββββββββ
β³ β³
β β
βββββββββββββ΄βββββββββββ βββββ΄ββββββββββββββββ
β logic_error β β runtime_error β
ββββββββββββββββββββββββ βββββββββββββββββββββ
β³ β³ β³ β³ β³ β³ β³ β³ β³
β β β β β β β β β
ββββββββββ΄ββββββ β β β β βββββββββ΄βββββββ β β β
β domain_error β β β β β β range_error β β β β
ββββββββββββββββ β β β β ββββββββββββββββ β β β
ββββββββββββββββββ΄ββ β β β ββββββββββββββββ΄ββ β β
β invalid_argument β β β β β overflow_error β β β
ββββββββββββββββββββ β β β ββββββββββββββββββ β β
ββββββββββββββ΄ββ β β βββββββββββββββββ΄ββ β
β length_error β β β β underflow_error β β
ββββββββββββββββ β β βββββββββββββββββββ β
ββββββββββββββ΄ββ β ββββββββββββββ΄ββ
β out_of_range β β β system_error β
ββββββββββββββββ β ββββββββββββββββ
ββββββββββββββ΄ββ
β future_error β
ββββββββββββββββ
try
#1
#include "Loud.h"
Loud a('a');
void foo() {
Loud b('b');
throw "oops!";
Loud c('c');
}
int main() {
Loud d('d');
try {
foo();
}
catch (const char *error) {
cout << "Caught: " << error << "\n";
}
Loud e('e');
return 0;
}
Loud::Loud() [c='a']
Loud::Loud() [c='d']
Loud::Loud() [c='b']
Loud::~Loud() [c='b']
Caught: oops!
Loud::Loud() [c='e']
Loud::~Loud() [c='e']
Loud::~Loud() [c='d']
Loud::~Loud() [c='a']
try
#2
#include "Loud.h"
Loud a('a');
void foo() {
Loud b('b');
throw "oops!";
Loud c('c');
}
void bar() {
Loud d('d');
foo();
}
int main() {
Loud e('e');
try {
bar();
}
catch (const char *error) {
cout << "Caught: “" << error << "”\n";
}
Loud f('f');
return 0;
}
Loud::Loud() [c='a']
Loud::Loud() [c='e']
Loud::Loud() [c='d']
Loud::Loud() [c='b']
Loud::~Loud() [c='b']
Loud::~Loud() [c='d']
Caught: “oops!”
Loud::Loud() [c='f']
Loud::~Loud() [c='f']
Loud::~Loud() [c='e']
Loud::~Loud() [c='a']
catch
#1
try {
throw "oops!";
}
catch (int i) {
cout << "int " << i << "\n";
}
catch (const char *error) {
cout << "C string: " << error << "\n";
}
catch (...) { // Gotta catch ’em all!
cout << "something\n";
}
C string: oops!
Multiple catches
- Multiple
catch
clauses are executed in order written.
- Therefore,
catch
the most specific things first,
and the least specific things last.
catch (...)
is the least specific of all, so it must go last.
- A good compiler may warn of an unreachable
catch
;
donβt count on it.
try { }
catch (...) { }
catch (int) { }
c.cc:2: error: '...' handler must be the last handler for its try block
catch
#2
try {
throw 42;
}
catch (short s) {
cout << "Got a short: " << s << "\n";
}
catch (long l) {
cout << "Got a long: " << l << "\n";
}
terminate called after throwing an instance of 'int'
SIGABRT: Aborted
The type must match. No conversions! (almost)
catch
#2Β½
try {
throw "Son of a gun!";
}
catch (string s) {
cout << "Got a string: " << s << "\n";
}
terminate called after throwing an instance of 'char const*'
SIGABRT: Aborted
Why didnβt that work?
catch
#2ΒΎ
try {
throw "That’s better."s;
}
catch (string s) {
cout << "Got a string: " << s << "\n";
}
Got a string: That’s better.
Hey, that worked!
catch
#2β
try {
throw "Even better."s;
}
catch (const string &s) {
cout << "Got a string: " << s << "\n";
}
Got a string: Even better.
This works, and is more efficient.
Of course, efficiency may not be an issue here.
catch
#3
// logic_error is-a exception
// runtime_error is-a exception
// overflow_error is-a runtime_error
try {
throw overflow_error("Just too big!");
}
catch (const logic_error &e) {
cout << "logic_error: " << e.what() << '\n';
}
catch (const runtime_error &e) {
cout << "runtime_error: " << e.what() << '\n';
}
catch (const exception &e) {
cout << "exception: " << e.what() << '\n';
}
runtime_error: Just too big!
catch
#3Β½
Avoid catching a polymorphic type by value,
because of object slicing:
try {
throw overflow_error("value");
}
catch (exception e) { // β β β πππ
cout << "caught: " << e.what() << '\n';
}
try {
throw overflow_error("const ref");
}
catch (const exception &f) {
cout << "caught: " << f.what() << '\n';
}
c.cc:4: warning: catching polymorphic type 'class std::exception' by value
caught: std::exception
caught: const ref
catch
#4
// logic_error is-a exception
// runtime_error is-a exception
// overflow_error is-a runtime_error
try {
throw overflow_error("Just too big!");
}
catch (const logic_error &e) {
cout << "logic_error: " << e.what() << '\n';
}
catch (const exception &e) {
cout << "exception: " << e.what() << '\n';
}
exception: Just too big!
No conversions, but is-a is good enough.
re-throw
void foo() {
throw string("Division by zero");
}
void bar() {
try {
foo();
}
catch (string msg) {
if (msg == "Out of memory") // I’ve got this!
/* get more memory */;
else
throw; // Throw up hands in despair.
}
}
int main() {
try {
bar();
}
catch (string problem) {
cout << "Problem in bar: " << problem << '\n';
}
return 0;
}
Problem in bar: Division by zero