Exception Handling
throw
→ throws an exception
→ followed by an argument
try { code that may throw an exception }
→ you place code that may throw an exception in a try block
→ if code throws exception the try block is exited
→ the thrown exception is handled by a catch handler
→ if no cath handler exists the programm terminates
catch (Exception ex) { code to handle the exception }
→ code that handles the exception
→ can have multiple catch handlers
→ may or may not cause the programm to terminate
// example
// what happens if total is zero
double average {};
if (total == 0)
// what to do?
else
average = sum / total;
double average {};
try { // try block
if (total == 0)
throw 0; // throw the exception
average = sum / total; // won't execute if total == 0
// use average here
}
catch (int &ex) { // exception handler
std::cerr << "can't divide by zero" << std::endl;
}
std::cout << "program continues" << std::endl;
Exception handling in functions
double calculate_avg(int sum, int total) {
if (total == 0)
throw 0;
return static_cast<double>(sum) / total;
}
/// function call
try {
average = calculate_avg(sum, total);
std::cout << average << std::endl;
}
catch (int &ex) {
std::cerr << "You can't divide by zero" << std::endl;
}
std::cout << "Bye" << std::endl;
double calculate_mpg(int miles, int gallons) {
if (gallons == 0)
throw 0;
if (miles < 0 || gallons < 0)
throw std::string{"Negative value error"};
return static_cast<double>(miles) / gallons;
}
// function call
double miles_per_gallon {};
try {
miles_per_gallon = calculate_mpg(miles, gallons);
std::cout << miles_per_gallon << std::endl;
}
catch (int &ex) {
std::cerr << "You can't divide by zero" << std::endl;
}
catch (std::string &ex) {
std::cerr << ex << std::endl;
}
catch (...) {
std::cerr << "Unknown exception" << std::endl;
}
std::cout << "Bye" << std::endl;
Stack unwinding
→ if an exception is thrown but not caught in the current scope, c++ tries to find a handler for the exception by unwinding the stack
→ function in which the exception was not caught terminates and is removed from the cell stack
→ if a try block was used or the catch handler doesn’t match stack unwinding occurs again
→ if the stack in unwound back to main and no catch handler handles the exception the program terminates
// example
void func_a() {
std::cout << "Starting func_a" << std::endl;
std::cout << "Ending func_a" << std::endl;
}
void func_b() {
std::cout << "Starting func_b" << std::endl;
try {
func_c();
}
catch (int &ex) {
std::cout << "Caught error in func_b" << std:endl;
}
std::cout << "Ending func_b" << std::endl;
}
void func_c() {
std::cout << "Starting func_c" << std::endl;
throw 100;
std::cout << "Ending func_c" << std::endl;
}
int main() {
std::cout << "Starting main" << std:endl;
try {
func_a();
}
catch (int &ex) {
std::cout << "Caught error in main" << std::endl;
}
std::cout << "Finishing main" << std::endl;
std::cout << "Finishing main" << std::endl;
return 0;
}
Exception Classes
→ we can create exception classes and throw instances of those classes
Best practice:
→ throw an object not a primitive type
→ throw an object by value
→ catch an object by reference (or const reference)
class DivideByZeroException {
};
class NegativeValueException {
};
double calculate_mpg(int miles, int gallons) {
if (gallons == 0)
throw DivideByZeroException();
if (miles < 0 || gallons < 0)
throw NegativeValueException();
return static_cast<double>(miles) / gallons;
}
// function call
try {
miles_per_gallon = calculate_mpg(miles, gallons);
std::cout << miles_per_gallon << std::endl;
}
catch (const DivideByZeroException &ex) {
std::cerr << "You can't divide by zero" << std::endl;
}
catch (const NegativeValueException &ex) {
std::cerr << "Negative values aren't allowed" << std::endl;
}
std::cout << "Bye" << std::endl;
Class-level exceptions
Exceptions can also be thrown within a class:
Method
→ these work the same way as they do for functions as we’ve seen
Constructor
→ Constructors may fail
→ Constructor do not return any value
→ throw an exception in the constructor if you cannot initialize an object
Destructor
→ Do NOT throw exceptions from your destructor
Account::Account(std::string name, double balance)
: name{name}, balance{balance} {
if (balance < 0.0)
throw IllegalBalanceException{};
}
//
try {
std::unique_ptr<Account> moes_Account =
std::make_unique<Checking_Account>("Moe",-10.0)
// use moes_account
}
catch (const IllegalBalanceException &ex) {
std::cerr << "Couldn't create account" << std::endl;
}