Hello! Today, we will learn about throwing exceptions in C++, which is vital for writing robust programs. Think of exceptions like traffic signals. When driving, if you see a red light, you stop to avoid collisions—a clear sign something needs attention. Similarly, exceptions signal problems in our code, allowing us to handle errors smoothly. By the end, you'll know how to throw and catch exceptions, making your programs more resilient.
To "throw" an exception means to signal that something unusual has occurred. In C++, we use the throw
keyword to do this. It’s like raising your hand to say, "Wait, there's a problem!"
Here’s a simple example. Imagine you ask a vending machine for a snack that's out of stock. The machine needs to inform you that it can't complete your request:
C++1#include <iostream> 2#include <stdexcept> // For std::runtime_error 3 4 5int main() { 6 int stock = 0; 7 if (stock == 0) { 8 throw std::runtime_error("Out of stock!"); 9 } 10 return 0; 11}
Output:
1terminate called after throwing an instance of 'std::runtime_error' 2 what(): Out of stock!
Imagine we have a function that can throw an error:
C++1#include <iostream> 2#include <stdexcept> 3 4void give_order(int order, int stock) { 5 if (order <= stock) { 6 std::cout << "Here is your order!" << std::endl; 7 } else { 8 throw std::runtime_error("Out of stock!"); 9 } 10} 11 12int main() { 13 give_order(1, 10); // Here is your order! 14}
In this case, everything is fine, and the function is successfully executed. But if we call it like this, it will throw an error:
C++1int main() { 2 give_order(5, 0); 3 // terminate called after throwing an instance of 'std::runtime_error' 4 // what(): Out of stock! 5}
This way, our function is designed to warn the program with an error that something went wrong. However, we don't want the program to terminate every time this error appears. Let's learn how to handle it.
In C++, we can handle exceptions with a special try-catch
block. The program tries to execute the code inside the try
, and in case it fails with an exception, executes the code inside the catch
.
Let’s modify our example:
C++1int main() { 2 int order = 5; 3 int stock = 3; 4 5 try { 6 give_order(order, stock); 7 } catch (const std::runtime_error& e) { 8 std::cout << "Error: " << e.what() << std::endl; 9 } 10 11 return 0; 12}
When the give_order
function is called with order = 5
and stock = 3
, it will throw a std::runtime_error
because the order exceeds the stock.
The catch
block catches exceptions of type std::runtime_error
. If the exception is caught, it prints the error message using e.what()
where e
is the exception object caught. This prevents the program from crashing and allows you to handle the error however you want.
This way, your program continues to run and provides a meaningful handling of an error.
Sometimes, different types of exceptions can occur, and it’s essential to handle each type uniquely. In C++, you can catch multiple exceptions by adding multiple catch
blocks. Each catch
block handles a specific type of exception.
Here’s a meaningful real-life example. Imagine a bank application where you need to process a transaction. Different errors can occur during the process, such as insufficient funds or an invalid account:
C++1#include <iostream> 2#include <stdexcept> 3 4void process_transaction(double amount, double balance, bool account_valid) { 5 if (!account_valid) { 6 throw std::invalid_argument("Invalid account."); 7 } 8 if (amount > balance) { 9 throw std::runtime_error("Insufficient funds."); 10 } 11 std::cout << "Transaction processed successfully!" << std::endl; 12}
The process_transaction
function can throw either a std::invalid_argument
if the account is invalid or a std::runtime_error
if there are insufficient funds.
C++1int main() { 2 double amount = 50.0; 3 double balance = 50.0; 4 bool account_valid = true; 5 6 try { 7 process_transaction(amount, balance, account_valid); 8 } catch (const std::invalid_argument& e) { 9 std::cout << "Error: " << e.what() << std::endl; 10 } catch (const std::runtime_error& e) { 11 std::cout << "Error: " << e.what() << std::endl; 12 } 13 14 return 0; 15}
In this code:
- In the
main
function, we try to callprocess_transaction
withaccount_valid = true
. - If a
std::invalid_argument
is thrown, it is caught by the firstcatch
block. - If a
std::runtime_error
is thrown, it is caught by the secondcatch
block.
This method allows you to handle different types of exceptions separately, providing more specific error management in your programs. In this case, the program will execute successfully and print out "Transaction processed successfully!"
. Let's look at other options.
Firstly, it is possible that the account is not valid:
C++1int main() { 2 double amount = 50.0; 3 double balance = 50.0; 4 bool account_valid = false; // Not valid! 5 6 try { 7 process_transaction(amount, balance, account_valid); 8 } catch (const std::invalid_argument& e) { // This exception is caught! 9 std::cout << "Error: " << e.what() << std::endl; // Error: Invalid account. 10 } catch (const std::runtime_error& e) { 11 std::cout << "Error: " << e.what() << std::endl; 12 } 13 14 return 0; 15}
In this case, the first catch
block works.
Secondly, it is possible that there are not enough funds on the balance:
C++1int main() { 2 double amount = 50.0; 3 double balance = 25.0; // Not enough! 4 bool account_valid = true; 5 6 try { 7 process_transaction(amount, balance, account_valid); 8 } catch (const std::invalid_argument& e) { 9 std::cout << "Error: " << e.what() << std::endl; 10 } catch (const std::runtime_error& e) { // This exception is caught! 11 std::cout << "Error: " << e.what() << std::endl; // Error: Insufficient funds. 12 } 13 14 return 0; 15}
In this case, the second catch
block works.
Lastly, there can be the both problems at once:
C++1int main() { 2 double amount = 50.0; 3 double balance = 25.0; // Not enough! 4 bool account_valid = false; // Not valid! 5 6 try { 7 process_transaction(amount, balance, account_valid); 8 } catch (const std::invalid_argument& e) { // This exception is caught! 9 std::cout << "Error: " << e.what() << std::endl; // Error: Invalid account. 10 } catch (const std::runtime_error& e) { 11 std::cout << "Error: " << e.what() << std::endl; 12 } 13 14 return 0; 15}
In this case, the first catch
block will work as it is the first one. The second catch
block won't be executed.
Great job! Today, you've learned how to throw and catch exceptions in C++. Just like having a good set of tools helps you fix things, knowing how to handle exceptions helps you fix problems in your code more smoothly.
To recap:
- Throwing exceptions signals that something has gone wrong, using the
throw
keyword. - Catching exceptions with
try
andcatch
blocks helps you manage these errors.
Now it’s time to put what you’ve learned into practice! You’ll move to hands-on coding exercises where you’ll throw, catch, and handle exceptions, making your programs more robust. Happy coding!