CS253: Software Development with C++

Fall 2019

Exceptions

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

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