Lesson 33
Exception Handling
Covers try/throw/catch syntax, multiple catch blocks, catch-all handlers, standard exceptions, and custom exception classes.
Lesson content
What is exception handling?
An exception is an unexpected event or runtime error that interrupts the normal flow of a program.
- Dividing by zero
- Opening a file that doesn't exist
- Running out of memory
- Invalid user input
- Accessing invalid resources
C++ provides three keywords to handle exceptions: try contains code that may generate an exception, throw signals that an exception has occurred, and catch handles the thrown exception.
Imagine you're driving a car. Normally, you continue driving (normal program flow). If you get a flat tire (exception), you stop and fix it (exception handling) instead of abandoning the car.
Why does exception handling exist?
Without exception handling, programs may crash unexpectedly, error-handling code becomes scattered throughout the program, and resources such as memory or files may not be released properly.
- Separating error-handling code from normal logic
- Improving program reliability
- Making applications easier to debug and maintain
Real-world use cases
- File handling
- Database operations
- Network communication
- Banking applications
- Input validation
- Memory allocation
- Web servers
Prerequisites
- Functions
- Classes and objects
- Constructors and destructors
- Dynamic memory (new and delete)
- Basic C++ syntax
Basic syntax
try
{
// Code that may throw an exception
}
catch(exceptionType variable)
{
// Code to handle the exception
}The throw keyword
The throw keyword is used to generate an exception.
throw "Division by zero!";The thrown value can be an integer, character, floating-point value, string literal, object, standard exception object, or custom exception object.
The catch block
A catch block receives and handles the exception.
catch(const char* message)
{
cout << message;
}The parameter type must match the type of the thrown exception.
The try block
All code that may produce an exception should be placed inside a try block.
try
{
// Risky code
}If no exception is thrown, the associated catch blocks are skipped.
Multiple catch blocks
Different exception types can be handled separately.
try
{
// Code
}
catch(int value)
{
cout << "Integer exception";
}
catch(double value)
{
cout << "Double exception";
}
catch(const char* message)
{
cout << message;
}Catch-all handler
A catch-all handler catches any exception type that hasn't been handled earlier.
catch(...)
{
cout << "Unknown exception.";
}This should typically be the last catch block.
Standard exceptions
The C++ Standard Library provides predefined exception classes in <stdexcept> and <exception>.
- std::runtime_error – runtime error
- std::logic_error – logical error
- std::invalid_argument – invalid function argument
- std::out_of_range – index out of range
- std::overflow_error – arithmetic overflow
- std::underflow_error – arithmetic underflow
- std::bad_alloc – memory allocation failure
Custom exceptions
You can create your own exception classes.
class InvalidAge
{
};
// Or derive from std::exception for better integration.Example 1: Basic try, throw, and catch
#include <iostream>
using namespace std;
int main()
{
try
{
throw "An error occurred.";
}
catch (const char* message)
{
cout << message << endl;
}
return 0;
}
// Output: An error occurred.Example 2: Division by zero
#include <iostream>
using namespace std;
int main()
{
int numerator = 10;
int denominator = 0;
try
{
if (denominator == 0)
{
throw "Division by zero is not allowed.";
}
cout << numerator / denominator << endl;
}
catch (const char* message)
{
cout << message << endl;
}
return 0;
}
// Output: Division by zero is not allowed.Example 3: Multiple catch blocks
#include <iostream>
using namespace std;
int main()
{
try
{
throw 100;
}
catch (int value)
{
cout << "Integer exception: " << value << endl;
}
catch (double value)
{
cout << "Double exception: " << value << endl;
}
return 0;
}
// Output: Integer exception: 100Example 4: Using standard exceptions
#include <iostream>
#include <stdexcept>
using namespace std;
int main()
{
try
{
throw invalid_argument("Age cannot be negative.");
}
catch (const invalid_argument& error)
{
cout << error.what() << endl;
}
return 0;
}
// Output: Age cannot be negative.Example 5: Custom exception class
#include <iostream>
#include <exception>
using namespace std;
class InvalidMarks : public exception
{
public:
const char* what() const noexcept override
{
return "Marks must be between 0 and 100.";
}
};
int main()
{
try
{
int marks = 120;
if (marks > 100)
{
throw InvalidMarks();
}
}
catch (const InvalidMarks& error)
{
cout << error.what() << endl;
}
return 0;
}
// Output: Marks must be between 0 and 100.Common mistakes and pitfalls
- Throwing exceptions for normal program flow. Fix: use exceptions only for exceptional or error conditions
- Catching large objects by value. Fix: catch exceptions by const reference
- Using catch(...) as the only handler. Fix: catch specific exception types whenever possible
- Ignoring resource cleanup when exceptions occur. Fix: use RAII and smart pointers to manage resources automatically
- Throwing raw pointers or dynamically allocated exceptions. Fix: throw objects by value and catch them by const reference
Best practices
- Throw exception objects, not primitive values or raw pointers
- Catch exceptions by const reference
- Use standard exception classes (std::runtime_error, std::invalid_argument, etc.) when appropriate
- Derive custom exceptions from std::exception
- Keep exception messages clear and descriptive
- Use RAII (Resource Acquisition Is Initialization) so resources are automatically cleaned up during stack unwinding
- Avoid throwing exceptions from destructors
When not to use this
- Handling expected outcomes, such as reaching the end of a file or validating routine user input
- Performance-critical code where exceptions would introduce unnecessary overhead
- A simple return value or status code communicates the result more clearly
- The situation is not truly exceptional
Summary
- Exception handling provides a structured way to detect and handle runtime errors
- C++ uses try, throw, and catch for exception handling
- Multiple catch blocks can handle different exception types
- catch(...) can handle unknown exceptions
- The Standard Library offers many useful exception classes
- Create custom exception classes by deriving from std::exception
- Catch exceptions by const reference to avoid unnecessary copies
- Use RAII to ensure resources are cleaned up automatically when exceptions occur
Frequently asked questions
What is exception handling in C++?
Exception handling is a mechanism that allows a program to detect and recover from runtime errors without crashing unexpectedly.
What is the purpose of try, throw, and catch?
try contains code that might generate an exception, throw signals that an exception has occurred, and catch handles the thrown exception.
Why should exceptions be caught by const reference?
Catching by const reference avoids unnecessary object copying and preserves the actual dynamic type of derived exception objects.
Can I create my own exception class?
Yes. You can define your own exception class, and it's recommended to derive it from std::exception and override the what() member function to provide an informative error message.
What is the difference between exceptions and error codes?
Exceptions separate error handling from normal logic and automatically propagate errors up the call stack, making them better suited for unexpected runtime errors. Error codes mix error handling with regular program flow and must be checked manually after each operation, often suitable for expected or routine outcomes.