Multiple catch statements in C++
In C++, multiple catch statements are used in exception handling to handle different types of exceptions that may occur during the execution of a program. Exceptions are unexpected events or errors that can disrupt the normal flow of a program. Using multiple catch statements, you can specify how each exception type should be handled, allowing for more fine-grained control over error handling. Here, we will discuss several catch statements in the C++.
Throwing Exceptions: Before discussing catch statements, it's essential to understand how exceptions are thrown in C++. Exceptions are objects of a class type derived from the std::exception class or any other user-defined exception class. They are thrown using the throw keyword when an error or exceptional condition occurs in your Code. See the example below:
throw MyException("An error occurred");
Single catch statement: In its simplest form, you can catch and handle exceptions using a single catch statement. This catch statement specifies the type of exception to catch and how to handle it.
Example:
try { //Code that may throw an exception } catch (const MyException& e) { // Handle MyException }
In this example, if a MyException is thrown in the try block, the Code inside the catch block will execute to handle the exception.
Multiple catch statements: You can have multiple catch blocks following a single try block to handle different types of exceptions. Each catch block catches a specific type of exception. Here's an example below:
try { //Code that may throw an exception } catch (const MyException& e) { // Handle MyException } catch (const AnotherException& e) { // Handle AnotherException } catch (const std::exception& e) { // Handle other exceptions derived from std::exception } catch (...) { // Catch-all for any other unhandled exceptions }
Explanation:
- The first catch block catches MyException.
- The second catch block catches AnotherException.
- The third catch block catches any exception derived from std::exception, providing a more general error-handling mechanism.
- The last catch block (catch (...)) serves as a catch-all for any exceptions not caught by the previous catch blocks.
Program:
#include#include class MyException : public std::exception { public: const char* what() const noexcept { return "MyException occurred"; } }; class AnotherException : public std::exception { public: const char* what() const noexcept { return "AnotherException occurred"; } }; int main() { try { //Code that may throw exceptions int choice; std::cout << "Enter 1 for MyException, 2 for AnotherException: "; std::cin >> choice; if (choice == 1) { throw MyException(); } else if (choice == 2) { throw AnotherException(); } else { throw std::runtime_error("Unknown choice"); } } catch (const MyException& e) { std::cerr << "Caught MyException: " << e.what() << std::endl; } catch (const AnotherException& e) { std::cerr << "Caught AnotherException: " << e.what() << std::endl; } catch (const std::exception& e) { std::cerr << "Caught a general exception: " << e.what() << std::endl; } catch (...) { std::cerr << "Caught an unknown exception." << std::endl; } return 0; }
Output:
Enter 1 for MyException, 2 for AnotherException: 1 Caught MyException: MyException occurred Enter 1 for MyException, 2 for AnotherException: 2 Caught AnotherException: AnotherException occurred
Explanation:
- In this program, we include necessary headers, such as
and , required for input/output and exception handling. - We define two custom exception classes, MyException and AnotherException, both of which are derived from the std::exception base class. These classes override the function to provide custom error messages when exceptions of their types are caught.
- In the main function, we use a try block to enclose the Code that may throw exceptions. The program prompts the user to enter either 1 or 2 to simulate throwing different exceptions.
- Inside the try block, we use conditional statements to simulate throwing either a MyException, AnotherException or a standard std::runtime_error exception based on the user's choice.
- After that, we have multiple catch blocks that correspond to different exception types. The catch blocks catch exceptions by type, starting with the most specific type (MyException and AnotherException) and moving to more general types (std::exception and a catch-all block).
- Each catch block prints an error message to std::cerr to indicate which exception was caught and its associated message.
- Finally, the program returns 0 to indicate successful execution.
Complexity analysis:
Time Complexity:
Time complexity measures an algorithm's time to run as a function of the input data size. It provides an upper bound on the running time in the worst-case scenario. Time complexity is typically expressed using big O notation (e.g., O(n), O(n log n), O(n^2)), where "n" represents the size of the input.
Common time complexities:
O(1) (constant time): The algorithm's execution time depends not on the input size; it's constant.
O(log n) (logarithmic time): The algorithm's execution time grows slowly as the input size increases (commonly found in binary search).
O(n) (linear time): The algorithm's execution time grows linearly with the input size (common in simple linear scans).
O(n log n) (linearithmic time): It is common in efficient sorting algorithms, like merge sort and quicksort.
O(n^2) (quadratic time): Execution time grows quadratically with input size (common in nested loops).
Space Complexity:
Space complexity measures the amount of memory space an algorithm uses as a function of the input size. It includes auxiliary and input space (additional memory used for data structures). Space complexity is also expressed using big O notation, similar to time complexity.
Common space complexities:
O(1) (constant space): The algorithm uses a fixed amount of memory regardless of input size.
O(n) (linear space): The amount of memory used grows linearly with input size.
O(n^2) (quadratic space): The algorithm uses a quadratic amount of memory with input size (common in nested data structures).
Approach 2:
Catch by Exception Type:
It is the most straightforward approach, where you have individual catch blocks for each specific exception type you expect to handle. Each catch block handles a different exception type. This method provides fine-grained control over handling different exception scenarios.
Program:
#include#include // Custom exception classes class MyException : public std::exception { public: const char* what() const noexcept { return "MyException occurred"; } }; class AnotherException : public std::exception { public: const char* what() const noexcept { return "AnotherException occurred"; } }; int main() { try { //Code that may throw exceptions int choice; std::cout << "Enter 1 for MyException, 2 for AnotherException: "; std::cin >> choice; if (choice == 1) { throw MyException(); } else if (choice == 2) { throw AnotherException(); } else { throw std::runtime_error("Unknown choice"); } } catch (const MyException& e) { // This catch block handles MyException std::cerr << "Caught MyException: " << e.what() << std::endl; } catch (const AnotherException& e) { // This catch block handles AnotherException std::cerr << "Caught AnotherException: " << e.what() << std::endl; } catch (const std::exception& e) { // This catch block handles other exceptions derived from std::exception std::cerr << "Caught a general exception: " << e.what() << std::endl; } catch (...) { // This catch block is a catch-all for any other unhandled exceptions std::cerr << "Caught an unknown exception." << std::endl; } return 0; }
Output:
Enter 1 for MyException, 2 for AnotherException: 2 Caught AnotherException: AnotherException occurred
Explanation:
Include Headers: In this example, the Code includes necessary C++ Standard Library headers for input/output operations and standard exception classes.
Exception Classes: The Code defines two custom exception classes, MyException and AnotherException. These classes are derived from the base class std::exception and override the what() function. When catching the exception, the what() function returns a description of the exception, which can be retrieved using the e.what() method.
Main Function: It is the entry point of the program.
- Inside the try block, we have a section of Code that may throw exceptions.
- The program prompts users to enter either 1 or 2 to simulate different exception scenarios.
- Depending on the user's input, it throws either a MyException, AnotherException or a standard std::runtime_error exception using the throw keyword.
Multiple catch Blocks:
- The program uses multiple catch blocks to handle different types of exceptions.
- The first catch block (catch (const MyException& e)) catches exceptions of type MyException. It prints a custom error message specific to this exception type using e.what() and std::cerr (standard error stream).
- The second catch block (catch (const AnotherException& e)) catches exceptions of type AnotherException. It prints a custom error message specific to this exception type.
- The third catch block (catch (const std::exception& e)) serves as a catch-all for exceptions derived from std::exception, which includes both MyException and AnotherException. It prints a generic error message.
- The last catch block (catch (...)) is a catch-all block that handles any other unhandled exceptions not caught by the previous blocks. It prints a message indicating that an unknown exception was caught.
Return Value: The program returns 0 to indicate successful execution.
Time Complexity:
The main() function contains several operations with constant time complexity:
Input operations (std::cin >> choice): O(1)
Conditional statements (if and else if checks): O(1)
Throwing exceptions: O(1)
- The try-catch block contains exception handling, but the complexity of catching exceptions is typically very low, especially if you need to perform more complex operations within the catch blocks. In practice, the time complexity of catching an exception is often considered O(1) or very close to it.
- The overall time complexity of this Code is primarily determined by the constant-time operations in the main() function and the exception handling, which can also be considered constant time. So, the Code has a time complexity of O(1).
Space Complexity:
The space complexity is determined by the memory used by variables and the call stack during function calls and exception handling.
Int choice: It takes up a constant amount of memory, O(1).
- const char* pointers in custom exception classes (MyException and AnotherException): These pointers point to constant strings and take up constant memory, O(1).
- The call stack is used to manage function calls and exceptions. In this Code, the depth of the call stack remains constant regardless of the input. Therefore, the space complexity of the call stack is O(1).
- When an exception is thrown, memory may be allocated for exception objects. However, it is typically a very small amount of memory, which is deallocated when the exception is caught. The space complexity for exception objects is generally considered O(1) for practical purposes.
Approach 3:
Catch by Base Class:
You can catch exceptions by their base class to group-related exceptions. This approach is useful when handling exceptions with similar characteristics together. It reduces redundancy in catch blocks but may require additional checks to determine the exact exception type.
Program:
#include#include // Custom exception classes class MyException : public std::exception { public: const char* what() const noexcept { return "MyException occurred"; } }; class AnotherException : public std::exception { public: const char* what() const noexcept { return "AnotherException occurred"; } }; int main() { try { //Code that may throw exceptions int choice; std::cout << "Enter 1 for MyException, 2 for AnotherException: "; std::cin >> choice; if (choice == 1) { throw MyException(); } else if (choice == 2) { throw AnotherException(); } else { throw std::runtime_error("Unknown choice"); } } catch (const std::exception& e) { // This catch block handles exceptions derived from std::exception if (typeid(e) == typeid(MyException)) { // Handle MyException std::cerr << "Caught MyException: " << e.what() << std::endl; } else if (typeid(e) == typeid(AnotherException)) { // Handle AnotherException std::cerr << "Caught AnotherException: " << e.what() << std::endl; } else { // Handle other exceptions derived from std::exception std::cerr << "Caught a general exception: " << e.what() << std::endl; } } catch (...) { // This catch block is a catch-all for any other unhandled exceptions std::cerr << "Caught an unknown exception." << std::endl; } return 0; }
Output:
Enter 1 for MyException, 2 for AnotherException: 1 Caught MyException: MyException occurred
Explanation:
Include Headers: In this example, the Code includes necessary C++ Standard Library headers for input/output operations and standard exception classes.
Custom Exception Classes: The Code defines two custom exception classes, MyException and AnotherException. These classes are derived from the base class std::exception. Each class overrides the what() function, which returns a description of the exception. In this case, the what() functions return custom error messages for each exception type.
Main Function: It is the entry point of the program.
- Inside the try block, we have a Code section that may throw exceptions.
- After that, the program prompts users to enter either 1 or 2 to simulate different exception scenarios.
- Depending on the user's input, it throws either a MyException, AnotherException or a standard std::runtime_error exception using the throw keyword.
Multiple catch Blocks:
- The program uses a single catch block to handle exceptions derived from std::exception, including MyException and AnotherException.
- Inside this catch block, typeid determines the actual type of the exception object (e).
- If the type matches MyException, it is handled as a MyException, and a custom error message specific to this exception type is printed using e.what() and std::cerr (standard error stream).
- If the type matches AnotherException, it is handled as an AnotherException, and a custom error message is printed.
- If the type does not match either of the specific exceptions, it is handled as a general exception derived from std::exception, and a generic error message is printed.
Catch-All Block: The Code also includes a catch-all block (catch (...)) to handle any other unhandled exceptions not caught by the previous blocks. This catch-all block prints a message indicating that an unknown exception was caught.
Return Value: The program returns 0 to indicate successful execution.
Time Complexity:
- User input (std::cin >> choice) is a constant time operation, as it does not depend on the input size.
- The Code contains conditional statements (if-else) and exception handling (try-catch) operations with constant time complexity. These operations do not scale with the input size or any data structures.
- The overall Code's time complexity is O(1), indicating that its execution time is constant and not influenced by the input or data size.
Space Complexity:
- The Code defines two custom exception classes (MyException and AnotherException) and uses standard library exceptions (std::exception, std::runtime_error).
- The memory usage for these exception classes and the stack space used for exception handling is constant and does not depend on the input size or data structures. Therefore, the space complexity is O(1).
Approach 4:
Catch by Reference or Pointer:
You can catch exceptions by reference or pointer to access more information about the exception object. It allows you to inspect the exception object's properties, such as error messages or custom data.
Program:
#include#include // Custom exception classes class MyException : public std::exception { public: const char* what() const noexcept { return "MyException occurred"; } }; class AnotherException : public std::exception { public: const char* what() const noexcept { return "AnotherException occurred"; } }; int main() { try { //Code that may throw exceptions int choice; std::cout << "Enter 1 for MyException, 2 for AnotherException: "; std::cin >> choice; if (choice == 1) { throw MyException(); } else if (choice == 2) { throw AnotherException(); } else { throw std::runtime_error("Unknown choice"); } } catch (const MyException& e) { // This catch block handles MyException std::cerr << "Caught MyException: " << e.what() << std::endl; } catch (const std::exception* e) { // This catch block handles other exceptions derived from std::exception if (e->what() == std::string("SomeError")) { // Handle specific error condition std::cerr << "Caught a specific error: " << e->what() << std::endl; } else { // Handle other exceptions derived from std::exception std::cerr << "Caught a general exception: " << e->what() << std::endl; } } catch (...) { // This catch block is a catch-all for any other unhandled exceptions std::cerr << "Caught an unknown exception." << std::endl; } return 0; }
Output:
Enter 1 for MyException, 2 for AnotherException: 1 Caught MyException: MyException occurred
Explanation:
Include Headers: In this example, the Code includes necessary C++ Standard Library headers for input/output operations and standard exception classes.
Custom Exception Classes: The Code defines two custom exception classes, MyException and AnotherException. These classes are derived from the base class std::exception. Each class overrides the what() function, which returns a description of the exception. In this case, the what() functions return custom error messages for each exception type.
Main Function: It is the entry point of the program.
- Inside the try block, we have a Code section that may throw exceptions.
- After that, the program prompts users to enter either 1 or 2 to simulate different exception scenarios.
- Depending on the user's input, it throws either a MyException, AnotherException, or a standard std::runtime_error exception using the throw keyword.
Multiple catch Blocks:
- The program uses multiple catch blocks to handle different types of exceptions.
- The first catch block (catch (const MyException& e)) catches exceptions of type MyException. It prints a custom error message specific to this exception type using e.what() and std::cerr (standard error stream).
- The second catch block (catch (const std::exception* e)) catches exceptions derived from std::exception by catching a pointer to an exception object. It allows us to access the exception object's properties directly.
- Inside this catch block, we check the value returned by e->what() (the what() function of the exception object) to determine the exact exception message.
- If the message matches "SomeError", we handle it as a specific error condition and print a custom message.
- If the message does not match the specific error message, we handle it as a general exception derived from std::exception and print a generic error message.
Catch-All Block: The Code also includes a catch-all block (catch (...)) to handle any other unhandled exceptions which is not caught by the previous blocks. This catch-all block prints a message indicating that an unknown exception was caught.
Return Value: The program returns 0 to indicate successful execution.
Time Complexity:
- The time complexity of the Code is primarily determined by the user's input and the Code's execution within the try block.
- Reading user input with std::cin is typically considered an O(1) operation because it takes a constant amount of time regardless of the input size.
- The Code inside the try block consists of conditional statements (if and else if) and exception throwing, all of which are O(1) operations in terms of time complexity.
- The catch blocks, including the checks for specific exception messages, are also O(1) operations because they do not depend on the input size.
- The time complexity of the provided Code is O(1) because the execution time remains relatively constant and does not depend on the input size.
Space Complexity:
Memory usage for these variables is constant and does not depend on the input size, so the space complexity is O(1).
The space complexity of the Code is determined by the memory required to store variables and objects.
In this code, the primary memory usage comes from storing the user's input (variable choice) and any exception objects that may be thrown (e.g., MyException or AnotherException objects).