Exception Handling in C++

In this article, we will cover exception handling in C++ in great detail. We will discuss the motivation behind using exception handling, the basics of how to use it, and some best practices. This article also includes examples to help illustrate the concepts.

Table of Contents

  1. Introduction
  2. Basic Exception Handling
    1. Throwing an Exception
    2. Catching an Exception
  3. Standard Exceptions
  4. Custom Exceptions
  5. Exception Propagation
  6. Best Practices
  7. Conclusion

1. Introduction

In programming, exceptions are unexpected events that occur during the execution of a program. They often result from erroneous input, hardware failures, or other exceptional conditions. Exception handling is a mechanism that allows developers to manage such situations gracefully, rather than letting the program crash or produce incorrect results.

C++ provides built-in support for exception handling through the try, catch, and throw keywords. This allows for a more structured and robust approach to error handling, making it easier to write reliable code.

2. Basic Exception Handling

2.1. Throwing an Exception

In C++, you can throw an exception using the throw keyword followed by an expression. This expression represents the exception object and can be of any data type, including built-in types (e.g., int, float, char), objects, or pointers.

Here is an example of throwing an exception when a function is called with an invalid argument:

void validateAge(int age) {
if (age < 0) {
throw "Invalid age: Age cannot be negative.";
}
}

2.2. Catching an Exception

To catch an exception, you need to enclose the code that may throw an exception within a try block. Then, you can specify one or more catch blocks to handle the exception(s). Each catch block must specify the type of exception it can handle.

Here’s an example of how to catch an exception:

#include <iostream>

int main() {
int age = -1;

try {
validateAge(age);
} catch (const char* errorMessage) {
std::cerr << "Caught exception: " << errorMessage << std::endl;
}

return 0;
}

In this example, the main function calls the validateAge function within a try block. If validateAge throws an exception, the catch block following the try block will handle it. The catch block specifies that it can handle exceptions of type const char*, which matches the type of exception thrown by validateAge.

3. Standard Exceptions

C++ provides a set of standard exceptions defined in the <stdexcept> header. These exceptions are derived from the std::exception class and are designed to cover common error situations.

Some common standard exceptions include:

  • std::invalid_argument
  • std::out_of_range
  • std::runtime_error
  • std::overflow_error
  • std::underflow_error

Here’s an example ofusing a standard exception:

#include <iostream>
#include <stdexcept>

void validateAge(int age) {
if (age < 0) {
throw std::invalid_argument("Invalid age: Age cannot be negative.");
}
}

int main() {
int age = -1;

try {
validateAge(age);
} catch (const std::invalid_argument& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}

return 0;
}

In this example, we replaced the const char* exception with the std::invalid_argument exception. When catching the exception, we specify the std::invalid_argument type in the catch block and use the what() member function to get the error message.

4. Custom Exceptions

Sometimes, the standard exceptions may not cover all error situations in your application. In such cases, you can create custom exception classes by inheriting from the std::exception class or any of its derived classes.

Here’s an example of a custom exception class:

#include <stdexcept>
#include <string>

class InvalidAgeException : public std::invalid_argument {
public:
explicit InvalidAgeException(const std::string& message) : std::invalid_argument(message) {}
};

To use this custom exception class, you can throw and catch it just like any other exception:

void validateAge(int age) {
if (age < 0) {
throw InvalidAgeException("Invalid age: Age cannot be negative.");
}
}

int main() {
int age = -1;

try {
validateAge(age);
} catch (const InvalidAgeException& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}

return 0;
}

5. Exception Propagation

Exceptions can propagate through function calls. If a function does not catch an exception, it will be propagated up the call stack until it is caught by a suitable catch block or reaches the main function. If an exception reaches the main function without being caught, the program will terminate and the unhandled exception message will be displayed.

Here’s an example of exception propagation:

void processData(int age) {
validateAge(age);
}

int main() {
int age = -1;

try {
processData(age);
} catch (const InvalidAgeException& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}

return 0;
}

In this example, the processData function calls validateAge, but does not catch the exception. The exception propagates up to the main function, where it is caught and handled.

6. Best Practices

Here are some best practices for exception handling in C++:

  1. Use exceptions for error handling, not for regular control flow.
  2. Use standard exceptions whenever possible, and create custom exceptions when necessary.
  3. Catch exceptions by reference, not by value.
  4. Don’t catch exceptions that you cannot handle. Let them propagate up the call stack to be handled at the appropriate level.
  5. Be mindful of resource management (e.g., memory, file handles) when dealing with exceptions. Use RAII (Resource Acquisition Is Initialization) to manage resources.
  6. Document the exceptions that your functions can throw, so that other developers know how to handle them.

7. Conclusion

In this article, we covered exception handling in C++ in great detail. We discussed the motivation behind using exception handling, the basics of how to use it, and some best practices. We explored how to throw and catch exceptions, use standard exceptions, create custom exceptions, and handle exception propagation.

By following the best practices and understanding the concepts discussed in this article, you can write more robust and reliable C++ code. Exception handling allows you to manage unexpected events in a structured way, making it easier to handle errors and edge cases in your applications.

Remember to use exceptions for error handling and not for regular control flow, and always document the exceptions your functions may throw. By following these guidelines and using the examples provided as a reference, you’ll be well-equipped to handle exceptions in your C++ projects.