Introduction
In this lab, you will learn the fundamental concepts of exception handling in C++. You will start by understanding how to throw and catch basic exceptions, which are a way to handle runtime errors or unexpected situations in your program. You will then explore the use of the try, catch, and throw keywords, as well as standard exception classes such as std::exception. Additionally, you will learn how to define custom exception classes and implement multiple catch blocks for different exception types. Finally, you will explore the use of nested try-catch blocks to handle exceptions at different levels of your program.
Throw and Catch Basic Exceptions
In this step, you'll learn the fundamental concepts of exception handling in C++, focusing on how to throw and catch basic exceptions. Exceptions are a way to handle runtime errors or unexpected situations in your program.
Open the WebIDE and create a new file called basic_exceptions.cpp in the ~/project directory:
touch ~/project/basic_exceptions.cpp
Add the following code to basic_exceptions.cpp:
#include <iostream>
#include <stdexcept>
int divide(int numerator, int denominator) {
// Throw an exception if denominator is zero
if (denominator == 0) {
throw std::runtime_error("Division by zero is not allowed!");
}
return numerator / denominator;
}
int main() {
try {
// Attempt a normal division
int result1 = divide(10, 2);
std::cout << "10 / 2 = " << result1 << std::endl;
// Attempt division by zero
int result2 = divide(10, 0);
std::cout << "This line will not be executed" << std::endl;
}
catch (const std::exception& e) {
// Catch and handle the exception
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}
Let's break down the key components:
throwkeyword:- Used to generate an exception when an error occurs
- In this example, we throw a
std::runtime_errorwhen division by zero is attempted
tryblock:- Contains code that might generate an exception
- Allows you to attempt risky operations
- If an exception occurs, program control moves to the
catchblock
catchblock:- Handles the exception thrown in the
tryblock - Catches exceptions of a specific type (here,
std::exception) - Uses
e.what()to get the error message
- Handles the exception thrown in the
Compile and run the program:
g++ basic_exceptions.cpp -o basic_exceptions
./basic_exceptions
Example output:
10 / 2 = 5
Error: Division by zero is not allowed!
Key points about basic exception handling:
- Exceptions provide a way to handle runtime errors gracefully
throwgenerates an exceptiontryandcatchwork together to manage exceptional situations- Prevents program crashes by providing controlled error handling
Understand try, catch, and throw Keywords
In this step, you'll dive deeper into the three key keywords of exception handling in C++: try, catch, and throw. These keywords work together to create a robust error-handling mechanism in your programs.
Open the WebIDE and create a new file called exception_keywords.cpp in the ~/project directory:
touch ~/project/exception_keywords.cpp
Add the following code to exception_keywords.cpp:
#include <iostream>
#include <string>
// Function that might throw an exception
int processAge(int age) {
// Throw an exception for invalid age
if (age < 0) {
throw std::string("Age cannot be negative");
}
if (age > 120) {
throw std::string("Age is unrealistically high");
}
return age;
}
int main() {
// First try block: handling age validation
try {
// Successful case
int validAge = processAge(25);
std::cout << "Valid age: " << validAge << std::endl;
// This will throw an exception
int invalidAge1 = processAge(-5);
std::cout << "This line will not be executed" << std::endl;
}
catch (const std::string& errorMessage) {
// Catch block to handle the thrown exception
std::cout << "Error: " << errorMessage << std::endl;
}
// Second try block: another example
try {
// This will throw another exception
int invalidAge2 = processAge(150);
std::cout << "This line will also not be executed" << std::endl;
}
catch (const std::string& errorMessage) {
std::cout << "Error: " << errorMessage << std::endl;
}
return 0;
}
Let's break down the key components:
throwkeyword:- Used to generate an exception when a specific condition is met
- Can throw different types of objects (strings, integers, custom objects)
- Immediately stops the current function's execution
tryblock:- Contains code that might generate an exception
- Allows you to attempt risky operations
- If an exception occurs, program control moves to the matching
catchblock
catchblock:- Catches and handles specific types of exceptions
- Can have multiple catch blocks for different exception types
- Prevents program from crashing by handling errors gracefully
Compile and run the program:
g++ exception_keywords.cpp -o exception_keywords
./exception_keywords
Example output:
Valid age: 25
Error: Age cannot be negative
Error: Age is unrealistically high
Key points about exception keywords:
throwsignals an error conditiontrydefines a block of code that might generate an exceptioncatchhandles the exception and prevents program termination- Exceptions provide a structured way to manage runtime errors
Use Standard Exception Classes (std::exception)
In this step, you'll learn about standard exception classes in C++ and how to use the std::exception hierarchy to handle different types of runtime errors. The C++ Standard Library provides a set of predefined exception classes that cover various error scenarios.
Open the WebIDE and create a new file called standard_exceptions.cpp in the ~/project directory:
touch ~/project/standard_exceptions.cpp
Add the following code to standard_exceptions.cpp:
#include <iostream>
#include <stdexcept>
#include <limits>
double divideNumbers(double numerator, double denominator) {
// Check for division by zero using std::runtime_error
if (denominator == 0) {
throw std::runtime_error("Division by zero is not allowed!");
}
return numerator / denominator;
}
void checkArrayIndex(int* arr, int size, int index) {
// Check for out-of-range access using std::out_of_range
if (index < 0 || index >= size) {
throw std::out_of_range("Array index is out of bounds!");
}
std::cout << "Value at index " << index << ": " << arr[index] << std::endl;
}
int main() {
try {
// Demonstrate division by zero exception
std::cout << "Attempting division:" << std::endl;
double result = divideNumbers(10, 0);
}
catch (const std::runtime_error& e) {
std::cout << "Runtime Error: " << e.what() << std::endl;
}
try {
// Demonstrate array index out of range exception
int numbers[] = {1, 2, 3, 4, 5};
int arraySize = 5;
std::cout << "\nAccessing array elements:" << std::endl;
checkArrayIndex(numbers, arraySize, 2); // Valid index
checkArrayIndex(numbers, arraySize, 10); // Invalid index
}
catch (const std::out_of_range& e) {
std::cout << "Out of Range Error: " << e.what() << std::endl;
}
return 0;
}
Let's explore the standard exception classes:
std::exception:- Base class for all standard exceptions
- Provides a virtual
what()method to get error description
Common derived exception classes:
std::runtime_error: For runtime errors that can only be detected during program executionstd::out_of_range: When an index or iterator is out of valid range- Other common classes include
std::logic_error,std::invalid_argument, etc.
Compile and run the program:
g++ standard_exceptions.cpp -o standard_exceptions
./standard_exceptions
Example output:
Attempting division:
Runtime Error: Division by zero is not allowed!
Accessing array elements:
Value at index 2: 3
Out of Range Error: Array index is out of bounds!
Key points about standard exception classes:
- Provide a structured way to handle different types of errors
- Each exception class serves a specific purpose
what()method returns a descriptive error message- Helps in creating more robust and informative error handling
Define Custom Exception Classes
In this step, you'll learn how to create your own custom exception classes in C++. Custom exceptions allow you to define specific error types tailored to your application's unique requirements.
Open the WebIDE and create a new file called custom_exceptions.cpp in the ~/project directory:
touch ~/project/custom_exceptions.cpp
Add the following code to custom_exceptions.cpp:
#include <iostream>
#include <string>
#include <stdexcept>
// Custom exception class for bank account errors
class InsufficientFundsException : public std::runtime_error {
public:
// Constructor that takes account balance and withdrawal amount
InsufficientFundsException(double balance, double amount)
: std::runtime_error("Insufficient funds"),
currentBalance(balance),
withdrawalAmount(amount) {}
// Method to get detailed error information
double getCurrentBalance() const { return currentBalance; }
double getWithdrawalAmount() const { return withdrawalAmount; }
private:
double currentBalance;
double withdrawalAmount;
};
class BankAccount {
private:
double balance;
public:
BankAccount(double initialBalance) : balance(initialBalance) {}
void withdraw(double amount) {
// Check if withdrawal amount exceeds current balance
if (amount > balance) {
throw InsufficientFundsException(balance, amount);
}
balance -= amount;
std::cout << "Withdrawal successful. Remaining balance: $"
<< balance << std::endl;
}
double getBalance() const { return balance; }
};
int main() {
try {
// Create a bank account with initial balance
BankAccount account(100.0);
// Attempt a valid withdrawal
account.withdraw(50.0);
// Attempt an invalid withdrawal
account.withdraw(75.0);
}
catch (const InsufficientFundsException& e) {
std::cout << "Error: " << e.what() << std::endl;
std::cout << "Current Balance: $" << e.getCurrentBalance() << std::endl;
std::cout << "Attempted Withdrawal: $" << e.getWithdrawalAmount() << std::endl;
}
return 0;
}
Let's break down the custom exception class:
Inherit from
std::runtime_error:- Provides a base for custom exception classes
- Allows using
what()method to get error description
Custom Exception Features:
- Constructor with additional error context
- Methods to retrieve specific error details
- Provides more informative error handling
Compile and run the program:
g++ custom_exceptions.cpp -o custom_exceptions
./custom_exceptions
Example output:
Withdrawal successful. Remaining balance: $50
Error: Insufficient funds
Current Balance: $50
Attempted Withdrawal: $75
Key points about custom exception classes:
- Inherit from standard exception classes
- Add specific error context and methods
- Provide more detailed error information
- Improve error handling and debugging
Implement Multiple catch Blocks for Different Exception Types
In this step, you'll learn how to handle multiple exception types using different catch blocks. This approach allows you to provide specific error handling for various types of exceptions that might occur in your program.
Open the WebIDE and create a new file called multiple_catch.cpp in the ~/project directory:
touch ~/project/multiple_catch.cpp
Add the following code to multiple_catch.cpp:
#include <iostream>
#include <stdexcept>
#include <string>
class InvalidAgeException : public std::runtime_error {
public:
InvalidAgeException(int age)
: std::runtime_error("Invalid age"), invalidAge(age) {}
int getInvalidAge() const { return invalidAge; }
private:
int invalidAge;
};
class Student {
private:
std::string name;
int age;
public:
void setStudent(const std::string& studentName, int studentAge) {
// Validate name length
if (studentName.length() < 2) {
throw std::length_error("Name is too short");
}
// Validate age range
if (studentAge < 0 || studentAge > 120) {
throw InvalidAgeException(studentAge);
}
name = studentName;
age = studentAge;
}
void displayInfo() const {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};
int main() {
Student student;
// First attempt: Short name
try {
student.setStudent("A", 25);
}
catch (const std::length_error& e) {
std::cout << "Length Error: " << e.what() << std::endl;
}
catch (const InvalidAgeException& e) {
std::cout << "Age Error: " << e.what()
<< " (" << e.getInvalidAge() << ")" << std::endl;
}
// Second attempt: Invalid age
try {
student.setStudent("John Doe", 150);
}
catch (const std::length_error& e) {
std::cout << "Length Error: " << e.what() << std::endl;
}
catch (const InvalidAgeException& e) {
std::cout << "Age Error: " << e.what()
<< " (" << e.getInvalidAge() << ")" << std::endl;
}
// Successful attempt
try {
student.setStudent("Alice", 20);
student.displayInfo();
}
catch (const std::length_error& e) {
std::cout << "Length Error: " << e.what() << std::endl;
}
catch (const InvalidAgeException& e) {
std::cout << "Age Error: " << e.what()
<< " (" << e.getInvalidAge() << ")" << std::endl;
}
return 0;
}
Let's break down multiple catch blocks:
Multiple
catchblocks:- Allow handling different types of exceptions
- Executed in order from most specific to most general
- Each block can handle a specific exception type
Exception Handling Strategy:
- First catch block handles
std::length_error - Second catch block handles
InvalidAgeException - Provides specific error messages for different scenarios
- First catch block handles
Compile and run the program:
g++ multiple_catch.cpp -o multiple_catch
./multiple_catch
Example output:
Length Error: Name is too short
Age Error: Invalid age (150)
Name: Alice, Age: 20
Key points about multiple catch blocks:
- Handle different exception types separately
- Provide specific error handling for each exception
- Order matters when catching exceptions
- Allows for more granular error management
Use Nested try-catch Blocks
In this step, you'll learn how to use nested try-catch blocks to handle complex error scenarios and provide more granular exception handling. Nested try-catch blocks allow you to handle exceptions at different levels of your code.
Open the WebIDE and create a new file called nested_exceptions.cpp in the ~/project directory:
touch ~/project/nested_exceptions.cpp
Add the following code to nested_exceptions.cpp:
#include <iostream>
#include <stdexcept>
#include <string>
class FileReadError : public std::runtime_error {
public:
FileReadError(const std::string& filename)
: std::runtime_error("Error reading file"), fileName(filename) {}
std::string getFileName() const { return fileName; }
private:
std::string fileName;
};
class DataProcessor {
public:
void processFile(const std::string& filename) {
try {
// Simulate file reading
if (filename.empty()) {
throw FileReadError("Empty filename");
}
std::cout << "Reading file: " << filename << std::endl;
try {
// Simulate data processing
validateData(filename);
}
catch (const std::runtime_error& e) {
std::cout << "Inner try-catch: Data validation error" << std::endl;
std::cout << "Error details: " << e.what() << std::endl;
// Rethrow the exception to outer catch block
throw;
}
}
catch (const FileReadError& e) {
std::cout << "Outer try-catch: File read error" << std::endl;
std::cout << "Filename: " << e.getFileName() << std::endl;
}
catch (...) {
std::cout << "Caught unknown exception" << std::endl;
}
}
private:
void validateData(const std::string& filename) {
// Simulate data validation
if (filename == "corrupt.txt") {
throw std::runtime_error("Corrupt file data");
}
std::cout << "Data validation successful" << std::endl;
}
};
int main() {
DataProcessor processor;
// Scenario 1: Empty filename
std::cout << "Scenario 1: Empty Filename" << std::endl;
processor.processFile("");
// Scenario 2: Corrupt file
std::cout << "\nScenario 2: Corrupt File" << std::endl;
processor.processFile("corrupt.txt");
// Scenario 3: Valid file
std::cout << "\nScenario 3: Valid File" << std::endl;
processor.processFile("data.txt");
return 0;
}
Let's break down nested try-catch blocks:
Outer try-catch block:
- Handles file-level exceptions
- Catches
FileReadErrorand other potential errors
Inner try-catch block:
- Handles data processing-specific exceptions
- Can rethrow exceptions to the outer catch block
Catch-all handler (
catch (...)):- Catches any unexpected exceptions
- Provides a final layer of error handling
Compile and run the program:
g++ nested_exceptions.cpp -o nested_exceptions
./nested_exceptions
Example output:
Scenario 1: Empty Filename
Outer try-catch: File read error
Filename: Empty filename
Scenario 2: Corrupt File
Reading file: corrupt.txt
Inner try-catch: Data validation error
Error details: Corrupt file data
Scenario 3: Valid File
Reading file: data.txt
Data validation successful
Key points about nested try-catch blocks:
- Provide multiple levels of exception handling
- Allow more detailed error management
- Can rethrow exceptions to outer catch blocks
- Useful for complex error scenarios
Summary
In this lab, you learned the fundamental concepts of exception handling in C++. You started by understanding how to throw and catch basic exceptions using the throw, try, and catch keywords. You explored the use of the std::runtime_error exception class to handle division by zero errors. Additionally, you learned how to access the error message using the e.what() function within the catch block. These techniques provide a way to handle runtime errors gracefully and prevent program crashes, allowing you to write more robust and reliable C++ applications.



