Catch All Exceptions in C++
Exceptional Handling in C++
Exception handling in C++ allows you to manage runtime errors and abnormal situations gracefully, allowing you to handle errors without abruptly terminating the program. C++ uses try, catch, and throw keywords for exception handling.
- Try block: The code that might throw an exception is placed inside a try block. If an exception is thrown within the try block, the control flow is transferred to the corresponding catch block.
- Catch block: A catch block follows the try block and specifies the type of exception it can handle. If an exception of that type (or a derived type) is thrown in the try block, the corresponding catch block will be executed. You can have multiple catch blocks for different exception types.
- Throw statement: The throw statement is used to throw an exception explicitly. When a particular condition or error occurs, it can be used inside the try block.
Example:
Here's a simple example to illustrate exception handling in C++:
#include <iostream>
#include <stdexcept>
int main() {
try {
int numerator = 10;
int denominator = 0;
if (denominator == 0) {
throw std::runtime_error("Division by zero is not allowed.");
}
int result = numerator / denominator;
std::cout << "Result: " << result << std::endl;
}
catch (const std::exception& e) {
std::cout << "Caught an exception: " << e.what() << std::endl;
}
catch (...) {
std::cout << "Caught an unknown exception." << std::endl;
}
std::cout << "Continuing execution after exception handling." << std::endl;
return 0;
}
In this example, the try block contains code that attempts to divide two numbers. If the denominator is zero, a std::runtime_error is explicitly thrown using the throw statement. The program then catches the exception in the catch block, prints the error message using e.what(), and continues execution after the catch block.
Output:
When running this code, the output will be:
Caught an exception: Division by zero is not allowed.
Continuing execution after exception handling.
Catching Specific Exception Types.
#include <iostream>
#include <stdexcept>
void riskyFunction() {
// Simulate an exception by throwing a runtime_error
throw std::runtime_error("This is a custom exception.");
}
int main() {
try {
riskyFunction();
}
catch (const std::runtime_error& e) {
std::cout << "Caught a runtime_error: " << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cout << "Caught a general exception: " << e.what() << std::endl;
}
std::cout << "Continuing execution after exception handling." << std::endl;
return 0;
}
Output:
Caught a runtime_error: This is a custom exception.
Continuing execution after exception handling.
Nested Try-catch Blocks
#include <iostream>
#include <stdexcept>
void riskyFunction() {
// Simulate an exception by throwing a runtime_error
throw std::runtime_error("This is a custom exception.");
}
int main() {
try {
try {
riskyFunction();
}
catch (const std::logic_error& e) {
std::cout << "Caught a logic_error: " << e.what() << std::endl;
// Rethrow the exception to the outer catch block
throw;
}
}
catch (const std::exception& e) {
std::cout << "Caught a general exception: " << e.what() << std::endl;
}
std::cout << "Continuing execution after exception handling." << std::endl;
return 0;
}
Output:
Caught a logic_error: This is a custom exception.
Caught a general exception: This is a custom exception.
Continuing execution after exception handling.
Example 1: Using std::bad_alloc for memory allocation failures:
#include <iostream>
#include <stdexcept>
int main() {
try {
int* bigArray = new int[1000000000000]; // Attempt to allocate a large array
delete[] bigArray;
}
catch (const std::bad_alloc& e) {
std::cout << "Caught a bad_alloc exception: " << e.what() << std::endl;
}
std::cout << "Continuing execution after exception handling." << std::endl;
return 0;
}
Output:
Caught a bad_alloc exception: std::bad_alloc
Continuing execution after exception handling.
Catch All Exceptions in C++
To catch all exceptions in C++, you can use a catch block with an ellipsis (...) as the exception type specifier. The ellipsis acts as a placeholder for any exception type that might be thrown within the corresponding try block.
As was previously mentioned, C++ has a wide variety of exception types. We must specify the kind of exception the catch block will catch in the code. In the last example, the throw statement was used to throw an int exception, and we said in the catch block that it would be caught.
Sometimes, we may run into complex exceptions that the compiler may throw due to some strange code. To catch all exceptions in C++, it is required to understand how to define a catch block.
We supply ellipses(...) in the catch block to catch all exceptions. This approach will effectively handle any software exceptions.
Example 1:
Here's an example of how you can catch all exceptions in C++:
#include <iostream>
int main() {
try {
// Some code that may throw an exception
throw "An error occurred!"; // Example of throwing an exception with a const char* message
}
catch (...) {
std::cout << "Caught an exception!" << std::endl;
}
std::cout << "Continuing execution after catching the exception." << std::endl;
return 0;
}
In this example, the catch block with ... will catch any exception thrown within the corresponding try block, regardless of its type. It acts as a catch-all exception handler.
Output:
When you run this code, you will see the output:
Caught an exception!
Continuing execution after catching the exception.
Example 2:
Here is a simple example of catching exceptions in C++:
#include <iostream>
int main() {
try {
//Your code goes here
throw "This is a test exception"; // throw a const char*
}
catch(...) {
std::cout << "Caught an exception" << std::endl;
}
return 0;
}
Output:
Exception caught
Example 3:
In this example, the catch-all block will catch any exception thrown within the try block. However, the catch-all block doesn't give you any information about the type of exception caught. So, it's best to use more specific catch blocks before the catch-all block if you need to handle different types of exceptions differently. Here's an example of how you can do this
#include <iostream>
#include <stdexcept>
int main() {
try {
//Your code goes here
throw std::runtime_error("This is a std::runtime_error"); // throw a std::runtime_error
}
catch(const std::runtime_error& re) {
// specific handling for runtime_error
std::cout << "Caught a runtime_error exception: " << re.what() << std::endl;
}
catch(const std::exception& e) {
// generic exception handling
std::cout << "Caught an exception of type std::exception: " << e.what() << std::endl;
}
catch(...) {
std::cout << "Caught an unknown exception" << std::endl;
}
return 0;
}
In this example, a std::runtime_error will be caught by the first catch block, the second catch block will catch any other exceptions that derive from std::exception, and the catch-all block will catch any other exceptions.
Example 4:
Here's a more complex example that involves different types of exceptions:
#include <iostream>
#include <stdexcept>
class CustomException: public std::exception {
public:
const char* what() const noexcept override {
return "This is a CustomException";
}
};
void riskyFunction(int value) {
switch (value) {
case 1:
throw std::runtime_error("This is a std::runtime_error");
Case 2:
throw std::out_of_range("This is a std::out_of_range exception");
Case 3:
throw CustomException();
Default:
Throw "An unknown exception occurred";
}
}
int main() {
try {
riskyFunction(2); // This will throw std::out_of_range exception
}
catch (const std::runtime_error& re) {
// specific handling for runtime_error
std::cout << "Caught a runtime_error exception: " << re.what() << std::endl;
}
catch (const std::out_of_range& oor) {
// specific handling for out_of_range
std::cout << "Caught an out_of_range exception: " << oor.what() << std::endl;
}
catch (const CustomException& ce) {
// specific handling for CustomException
std::cout << "Caught a CustomException: " << ce.what() << std::endl;
}
catch (const std::exception& e) {
// generic exception handling
std::cout << "Caught an exception of type std::exception: " << e.what() << std::endl;
}
catch (...) {
std::cout << "Caught an unknown exception" << std::endl;
}
return 0;
}
In this example, riskyFunction can throw different types of exceptions. main then calls riskyFunction and uses multiple catch blocks to handle the different types of exceptions that riskyFunction might throw. Each catch block is specific to an exception type, allowing for type-specific error handling.
Note: noexcept is a keyword added in C++11. A noexcept function is a function that does not throw exceptions and is specified by appending the noexcept keyword at the end of the function prototype. It guarantees the caller function that no exception will be thrown from this function.
Example 5:
Let's make an example using multiple custom exception types:
#include <iostream>
#include <stdexcept>
class CustomException1 : public std::exception {
public:
const char* what() const noexcept override {
return "CustomException1 occurred";
}
};
class CustomException2 : public std::exception {
public:
const char* what() const noexcept override {
return "CustomException2 occurred";
}
};
void riskyFunction(int value) {
switch (value) {
case 1:
throw CustomException1();
Case 2:
throw CustomException2();
Case 3:
throw std::runtime_error("This is a std::runtime_error");
Default:
Throw "An unknown exception occurred";
}
}
int main() {
try {
for(int i = 0; i < 5; i++){
riskyFunction(i); // This will throw different exceptions based on i's value
}
}
catch (const CustomException1& ce1) {
// specific handling for CustomException1
std::cout << "Caught a CustomException1: " << ce1.what() << std::endl;
}
catch (const CustomException2& ce2) {
// specific handling for CustomException2
std::cout << "Caught a CustomException2: " << ce2.what() << std::endl;
}
catch (const std::runtime_error& re) {
// specific handling for runtime_error
std::cout << "Caught a runtime_error exception: " << re.what() << std::endl;
}
catch (const std::exception& e) {
// generic exception handling
std::cout << "Caught an exception of type std::exception: " << e.what() << std::endl;
}
catch (...) {
std::cout << "Caught an unknown exception" << std::endl;
}
return 0;
}
In this example, riskyFunction can throw multiple types of exceptions depending on the input value. We've also created two custom exceptions: CustomException1 and CustomException2. The main function is now using a loop to call riskyFunction with different inputs, and it uses multiple catch blocks to handle the various exceptions that can be thrown.
Remember, the order of catch blocks matters. The first matching catch block will handle the exception, so more specific catch blocks should be placed before more general ones to ensure they are not bypassed.
Example 6:
Now, let's consider a real-world example where we simulate a system of a bank. In this case, we can have custom exceptions for common issues like insufficient funds or invalid account operations. Let's also consider an unknown exception for any other potential errors:
#include <iostream>
#include <stdexcept>
class InsufficientFundsException: public std::exception {
public:
const char* what() const noexcept override {
return "Insufficient funds for the transaction";
}
};
class InvalidAccountOperationException: public std::exception {
public:
const char* what() const noexcept override {
return "Invalid operation on the account";
}
};
void performBankOperation(int operationCode, double accountBalance, double transactionAmount) {
switch (operationCode) {
case 1: // Withdraw operation
if(accountBalance < transactionAmount) {
throw InsufficientFundsException();
}
break;
Case 2: // Some other operation that is not allowed
throw InvalidAccountOperationException();
default:
Throw "An unknown operation exception occurred";
}
}
int main() {
try {
performBankOperation(1, 100.0, 200.0); // This will throw an InsufficientFundsException
}
catch (const InsufficientFundsException& ife) {
std::cout << "Caught an InsufficientFundsException: " << ife.what() << std::endl;
}
catch (const InvalidAccountOperationException& iaoe) {
std::cout << "Caught an InvalidAccountOperationException: " << iaoe.what() << std::endl;
}
catch (const std::exception& e) {
std::cout << "Caught an exception of type std::exception: " << e.what() << std::endl;
}
catch (...) {
std::cout << "Caught an unknown exception" << std::endl;
}
return 0;
}
In this example, performBankOperation simulates performing a banking operation. If the operation code is 1 (withdraw operation), and the account balance is less than the transaction amount, it throws an InsufficientFundsException. If the operation code is 2, it throws an InvalidAccountOperationException. If the operation code is anything else, it throws a string indicating an unknown operation exception.
In the main function, we perform a bank operation, which would result in an InsufficientFundsException. We then use multiple catch blocks to handle different types of exceptions.
As in the previous examples, more specific catch blocks should be placed before more general ones to ensure the first matching exception is caught. More specific exception handlers would be bypassed if a more general exception were caught first and later.
Conclusion
Exception handling in C++ allows you to manage runtime errors and abnormal situations gracefully, allowing you to handle errors without abruptly terminating the program.
C++ uses try, catch, and throw keywords for exception handling. In the catch block, we need to mention the type of exception it will catch. In our previous example, an int exception was thrown using the throw statement, and in the catch block, we mentioned that it would catch the int exception.