In this tutorial, we will explore advanced error handling techniques in C++ using try, catch, and throw. We'll dive into the exception hierarchy, learn how to create custom exceptions, and understand the importance of asserts for debugging. By the end of this lesson, you'll be equipped with robust tools to handle errors gracefully and ensure your programs run smoothly.
Exception handling is a critical aspect of writing reliable software. It allows your program to respond appropriately when unexpected situations arise, preventing crashes and ensuring that resources are properly managed. In C++, exceptions provide a structured way to handle runtime errors, making your code more robust and maintainable.
In this tutorial, we'll cover the following topics:
try, catch, and throw.Exception handling in C++ is based on three keywords: try, catch, and throw. Here's a simple example to illustrate how they work:
1#include <iostream>23void divide(int numerator, int denominator) {4if (denominator == 0) {5throw "Division by zero error!";6}7std::cout << "Result: " << numerator / denominator << std::endl;8}910int main() {11try {12divide(10, 0);13} catch (const char* msg) {14std::cerr << "Error: " << msg << std::endl;15}16return 0;17}
Error: Division by zero error!
In this example:
divide() function throws an exception if the denominator is zero.try block contains code that might throw an exception.catch block catches and handles the exception, printing an error message.C++ provides a hierarchy of exceptions, all derived from the base class std::exception. This allows you to catch specific types of exceptions or handle them more generally. Here's a simple example demonstrating this:
1#include <iostream>2#include <stdexcept>34void riskyFunction(int value) {5if (value < 0) {6throw std::invalid_argument("Negative value provided");7}8if (value > 100) {9throw std::out_of_range("Value out of range");10}11}1213int main() {14try {15riskyFunction(200);16} catch (const std::out_of_range& e) {17std::cerr << "Out of range error: " << e.what() << std::endl;18} catch (const std::invalid_argument& e) {19std::cerr << "Invalid argument error: " << e.what() << std::endl;20} catch (const std::exception& e) {21std::cerr << "General exception: " << e.what() << std::endl;22}23return 0;24}
Out of range error: Value out of range
In this example:
std::invalid_argument and std::out_of_range are specific exception types.catch blocks catch these exceptions in a specific order, allowing you to handle different errors differently.You can create your own custom exceptions by inheriting from std::exception. This allows you to provide more detailed error information and handle specific error cases in a more organized way. Here's an example:
1#include <iostream>2#include <stdexcept>34class MyException : public std::exception {5public:6const char* what() const noexcept override {7return "My custom exception";8}9};1011void riskyFunction(int value) {12if (value == 0) {13throw MyException();14}15}1617int main() {18try {19riskyFunction(0);20} catch (const MyException& e) {21std::cerr << "Caught my custom exception: " << e.what() << std::endl;22} catch (const std::exception& e) {23std::cerr << "General exception: " << e.what() << std::endl;24}25return 0;26}
Caught my custom exception: My custom exception
In this example:
MyException is a custom exception class derived from std::exception.what() method returns a descriptive error message.Asserts are a powerful tool for debugging. They allow you to verify assumptions in your code and ensure that certain conditions are met. C++ provides two types of asserts: runtime asserts (assert()) and compile-time asserts (static_assert).
assert()Runtime asserts check conditions at runtime and terminate the program if the condition is false. Here's an example:
1#include <iostream>2#include <cassert>34void process(int value) {5assert(value > 0 && "Value must be positive");6std::cout << "Processing value: " << value << std::endl;7}89int main() {10process(-5);11return 0;12}
Assertion failed: Value must be positive, file runtime_assert.cpp, line 7
In this example:
assert() macro checks if the condition is true.static_assertCompile-time asserts check conditions at compile time. They are useful for validating template parameters or other constants. Here's an example:
1#include <iostream>23template<typename T>4void process(T value) {5static_assert(std::is_integral<T>::value, "T must be an integral type");6std::cout << "Processing value: " << value << std::endl;7}89int main() {10process(10);11// process(3.14); // Uncommenting this line will cause a compile-time error12return 0;13}
compile_time_assert.cpp: In instantiation of 'void process(T) [with T = double]': compile_time_assert.cpp:12:9: required from here compile_time_assert.cpp:6:5: error: static assertion failed: T must be an integral type
In this example:
static_assert() checks if the condition is true at compile time.what() methods and assert statements to make debugging easier.-DDEBUG) to catch potential issues early.Let's put everything together in a practical example. We'll create a simple calculator that performs basic arithmetic operations and handles errors gracefully.
1#include <iostream>2#include <stdexcept>34class DivisionByZeroException : public std::exception {5public:6const char* what() const noexcept override {7return "Division by zero error";8}9};1011double divide(double numerator, double denominator) {12if (denominator == 0) {13throw DivisionByZeroException();14}15return numerator / denominator;16}1718int main() {19try {20std::cout << "Enter two numbers: ";21double a, b;22std::cin >> a >> b;2324std::cout << "Result of division: " << divide(a, b) << std::endl;25} catch (const DivisionByZeroException& e) {26std::cerr << "Error: " << e.what() << std::endl;27} catch (const std::exception& e) {28std::cerr << "General error: " << e.what() << std::endl;29}30return 0;31}
In this example:
DivisionByZeroException for division by zero errors.divide() function throws this exception if the denominator is zero.main() function handles these exceptions and provides appropriate error messages.In this tutorial, you learned about advanced C++ concepts related to exception handling, asserts, and debugging. Key takeaways include:
try, catch, and throw for basic exception handling.assert()) and compile-time (static_assert) asserts for debugging.Now that you have a solid understanding of exception handling and debugging in C++, the next topic is Multithreading. Multithreading allows your programs to perform multiple tasks concurrently, improving performance and responsiveness. In the next lesson, we'll explore how to create and manage threads in C++ using the <thread> library.
Stay tuned for more advanced topics in C++ programming!