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;
}