× (C++)
Learn × (C++) step by step with clear examples and exercises.
Title: Mastering Exception Handling in C++
Why This Matters
Exception handling is a crucial aspect of C++ programming that provides a robust way to manage runtime errors effectively. It allows developers to write cleaner, more efficient code by separating error-handling logic from the main program flow. This results in more reliable and easier-to-debug programs, especially when dealing with user input, file I/O, or complex algorithms where errors can occur unexpectedly.
Prerequisites
Before delving into exception handling, it is essential to have a solid understanding of the following concepts:
- Basic C++ syntax and control structures (e.g., loops, if-else statements)
- Understanding of classes and objects in C++
- Knowledge of function overloading and operator overloading
- Familiarity with file I/O operations in C++
- Adequate understanding of exception handling basics (if you're new to the topic, consider reading about it before proceeding)
Core Concept
Exception handling in C++ revolves around the try, catch, and throw keywords. The try block encompasses code that may potentially throw exceptions, while the catch block handles those exceptions when they occur. When an exception is thrown within a try block, control is transferred to the appropriate catch block for error handling.
Custom Exception Classes
Creating custom exception classes can be beneficial for better error messages and more specific handling of exceptions. To define a custom exception class, inherit from the std::exception base class:
#include <stdexcept>
class MyException : public std::exception {
public:
MyException(const char* message) : std::exception(message) {}
const char* what() const noexcept override {
return message;
}
private:
const char* message;
};
Throwing and Catching Exceptions
Here's a simple example of exception handling using custom exceptions:
#include <iostream>
#include "MyException.h"
void read_input(int& input) {
if (std::cin.peek() != '\n') {
throw MyException("Invalid input - expected newline");
}
if (!(std::cin >> input)) {
throw MyException("Invalid input - failed to read integer");
}
}
int main() {
int input;
try {
read_input(input);
// Rest of the code that uses the input...
} catch (const MyException& ex) {
std::cerr << "Error: " << ex.what() << '\n';
return 1;
}
return 0;
}
In this example, we define a custom exception class MyException. We then use it in our read_input function to handle invalid input scenarios. If the input is not a newline or an integer, an instance of MyException is thrown and caught by the corresponding catch block.
Worked Example
Let's implement a simple function that reads a file line by line and calculates the sum of its numbers:
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <vector>
#include "MyException.h"
std::vector<int> read_numbers(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
throw MyException("Could not open file: " + filename);
}
std::vector<int> numbers;
int number;
while (file >> number) {
numbers.push_back(number);
}
if (file.peek() != EOF) {
throw MyException("Unexpected character found in file: " + filename);
}
return numbers;
}
int main() {
try {
const std::string filename = "numbers.txt";
auto numbers = read_numbers(filename);
int sum = 0;
for (const auto& number : numbers) {
sum += number;
}
std::cout << "Sum of numbers in " << filename << ": " << sum << '\n';
} catch (const MyException& ex) {
std::cerr << "Error: " << ex.what() << '\n';
return 1;
} catch (const std::exception& ex) {
std::cerr << "Error: " << ex.what() << '\n';
return 1;
}
return 0;
}
In this example, we define a function read_numbers that reads numbers from a file and returns them in a vector. If the file cannot be opened or an unexpected character is found, an exception is thrown using our custom MyException class. The main function calls read_numbers and calculates the sum of the numbers. If an exception occurs during the reading process, it is caught and handled by the corresponding catch block.
Common Mistakes
- Forgetting to include necessary headers (e.g., ``)
- Throwing exceptions without a matching
catchblock - Not properly initializing exception objects (use
std::runtime_erroror custom exception classes) - Failing to check for exceptions when calling functions that may throw exceptions
- Using raw pointers in the context of exception handling (avoid using raw pointers whenever possible)
- Neglecting to handle multiple types of exceptions with separate
catchblocks - Not properly declaring exception classes as
noexceptornothrowwhere appropriate - Overusing exceptions for non-critical errors, leading to performance issues and cluttered code
Practice Questions
- Modify the
read_numbersfunction to handle cases where the file contains non-numeric characters using a custom exception class. - Implement a custom exception class for handling division by zero errors in C++.
- Write a function that calculates the factorial of a number using exception handling to ensure the input is positive and within a specific range.
- Create a custom exception class for handling file-related errors (e.g., unable to read from or write to a file) and use it in a program that reads and writes data to multiple files.
- Implement a function that validates user input using regular expressions and throws exceptions for invalid inputs.
FAQ
Q: What happens if an uncaught exception occurs?
A: If an uncaught exception occurs, the program terminates immediately with an error message.
Q: Can I catch exceptions of specific types?
A: Yes, you can use multiple catch blocks to handle exceptions of specific types.
Q: Is it a good practice to use exceptions for every potential error scenario?
A: No, excessive use of exceptions can lead to performance issues and make the code harder to read. Use exceptions judiciously for critical errors that require immediate attention.
Q: How do I properly declare exception classes as noexcept or nothrow?
A: To declare an exception class as noexcept, use the following syntax: class MyException : public std::exception { ... } noexcept(true);. To declare it as nothrow, use noexcept(false).
Q: How can I handle exceptions thrown by functions in a library or third-party code?
A: If you cannot modify the library's source code, you should catch and handle exceptions at the point of call. Be aware that this might not always be possible or practical, depending on the specific library and use case.
Q: What is the difference between std::runtime_error and std::logic_error?
A: std::runtime_error represents errors that occur during program execution, such as I/O errors or network failures. std::logic_error, on the other hand, represents logic errors, like division by zero or invalid argument values.