1. Home
C++ Tutorial

Explore C++ Tutorials: Exploring the World of C++ Programming

Discover comprehensive C++ tutorials designed for beginners and advanced programmers alike. Enhance your coding skills with step-by-step guides and practical examples.

  • 77 Lessons
  • 15 Hours
right-top-arrow
37

Exception Handling in C++: A Comprehensive Guide | upGrad

Updated on 01/10/2024417 Views

Exception handling in C++ is a powerful mechanism that lets us developers manage unexpected events or errors that can occur during program execution. These unexpected events, called exceptions, can disrupt the normal flow of our code and, if left unhandled, lead to crashes, data corruption, or unpredictable behavior.

Let us learn how to deal with any type of C++ exception and handle them gracefully.

What Are Exceptions?

We can think of exceptions as alarms that go off when something goes wrong in our program. They signal situations that your code wasn't designed to handle directly. Some common types of exception handling in C++ are:

1. Runtime Errors

  • Divide by Zero: Attempting to divide a number by zero is mathematically impossible and triggers an exception.
  • Null Pointer Dereference: Trying to access a variable through a pointer that does not point to a valid memory location.
  • Index Out of Bounds: Accessing an array element using an index that's beyond the array's limits.

2. Logic Errors

  • Invalid Input: A user entering data that does not conform to the expected format (e.g., entering letters when numbers are expected).

3. Resource Issues

  • File Not Found: Trying to open a file that does not exist.
  • Out-of-Memory: Our program attempting to use more memory than is available.

Why Exception Handling in C++ Matters

Robust CPP exception handling is vital for building reliable and user-friendly software:

  • Prevent Program Crashes: Without exception handling in C++, an unhandled exception can abruptly terminate our program, leaving the user with a frustrating experience and potentially losing data.
  • Graceful Recovery: Exception handling in C++ allows us to gracefully recover from errors. Instead of crashing, we can:
    • Provide informative error messages to the user.
    • Log the error details for debugging purposes.
    • Attempt to fix the problem or offer alternative actions.
  • Informative Error Messages: By catching exceptions, we can provide meaningful error messages to the user or log detailed information for developers to diagnose and fix issues.
  • Maintain Program Integrity: Exception handling in C++ helps ensure that our program's data remains consistent and that resources are released properly, even in the face of errors. It prevents our application from entering an invalid state.

Exception Handling Mechanism in C++

C++ provides a structured way to detect, handle, and recover from a C++ exception using try-catch blocks, exception throwing, and a hierarchy of exception classes.

try-catch Blocks

The try-catch construct is the heart of C++ exception handling. It consists of two main parts:

  1. try Block: This block encloses the code that we suspect might throw an exception during execution. We place the potentially problematic code within the try block to monitor it.
  1. catch Block(s): These blocks follow the try block. Each catch block is designed to handle a specific type of exception. If exceptions of the specified types are thrown within the try blocks, the corresponding catch blocks are executed. We can have multiple catch blocks to handle different exception types. We refer to this as multiple exception handling in C++.

Example:

try {

// Code that might throw an exception (e.g., division by zero)

int result = numerator / denominator;

} catch (const std::runtime_error& e) {

// Handle std::runtime_error (e.g., division by zero)

std::cerr << "Error: " << e.what() << std::endl;

} catch (const std::exception& e) {

// Handle any other standard exception

std::cerr << "Error: " << e.what() << std::endl;

}

In the above exception handling in C plus plus example, the code inside the try block is executed and if an exception occurs, the program immediately jumps to the appropriate catch block that matches the exception's type. The code within the matching catch block is executed to handle the exception. If no exception occurs, the catch blocks are skipped.

Throwing Exceptions

throw Keyword: The throw keyword is used to manually signal that an exceptional situation has arisen. We throw an object of an exception class to indicate the error when using throw exception C++.

Example:

if (denominator == 0) {

throw std::runtime_error("Division by zero error!");

}

Common scenarios:

  • Invalid Input: When user input does not meet the required criteria.
  • Resource Failures: When the program cannot open a file, connect to a network, or allocate memory.
  • Custom Errors: When we define our own specific error conditions.

Exception Hierarchy

C++ provides a hierarchy of standard exception classes:

  • std::exception: The base class for all standard exceptions.
  • std::runtime_error: Derived from std::exception, used for runtime errors (e.g., std::range_error, std::overflow_error).
  • std::logic_error: Derived from std::exception, used for logic errors (e.g., std::invalid_argument, std::domain_error).

We can create custom exception classes by deriving them from these standard classes. This allows us to define specialized exception types that better describe our specific error conditions.

The catch(...) Block (Catch-All)

This special catch block, written as catch (...), acts as a safety net. It catches any exception that hasn't been caught by the previous, more specific catch blocks. However, we should use it with caution as it can make debugging difficult because we will not know the exact type of exception that occurred.

Exception Handling in C++ Program

Here is a C++ exception handling example that you can try out yourself:

#include <iostream>

#include <stdexcept> // For std::runtime_error

double divideNumbers(double numerator, double denominator) {

if (denominator == 0) {

throw std::runtime_error("Error: Division by zero is not allowed.");

}

return numerator / denominator;

}

int main() {

double num1, num2, result;

std::cout << "Enter two numbers: ";

std::cin >> num1 >> num2;

try {

result = divideNumbers(num1, num2);

std::cout << "Result: " << result << std::endl;

} catch (const std::runtime_error& e) {

std::cerr << e.what() << std::endl; // Print the error message

}

return 0;

}

Scenario 1:

#include <iostream>

#include <stdexcept> // For std::runtime_error

double divideNumbers(double numerator, double denominator) {

if (denominator == 0) {

throw std::runtime_error("Error: Division by zero is not allowed.");

}

return numerator / denominator;

}

int main() {

double num1, num2, result;

std::cout << "Enter two numbers: ";

std::cin >> num1 >> num2;

try {

result = divideNumbers(num1, num2);

std::cout << "Result: " << result << std::endl;

} catch (const std::runtime_error& e) {

std::cerr << e.what() << std::endl; // Print the error message

}

return 0;

}

Output:

Enter two numbers: 1 4

Result: 0.25

Scenario 2:

#include <iostream>

#include <stdexcept> // For std::runtime_error

double divideNumbers(double numerator, double denominator) {

if (denominator == 0) {

throw std::runtime_error("Error: Division by zero is not allowed.");

}

return numerator / denominator;

}

int main() {

double num1, num2, result;

std::cout << "Enter two numbers: ";

std::cin >> num1 >> num2;

try {

result = divideNumbers(num1, num2);

std::cout << "Result: " << result << std::endl;

} catch (const std::runtime_error& e) {

std::cerr << e.what() << std::endl; // Print the error message

}

return 0;

}

Output:

Enter two numbers: 1 0

Error: Division by zero is not allowed.

In this C++ exception handling example, the user is asked to input two numbers and the program attempts to divide the numbers using the divideNumbers function.

There are two scenarios in the above exception handling in C++ example:

  • Scenario 1 (Normal division): If the denominator is not zero, the division is successful, the result is printed, and the program ends normally.
  • Scenario 2 (Division by zero): If the denominator is zero, the divideNumbers function throws a std::runtime_error exception. The program immediately jumps to the catch block. The error message from the exception is printed to the console (using std::cerr for error output). The program continues execution after the catch block.

If you wish to learn how to code in C++, you can check out upGrad’s software engineering courses.

Advanced Exception Handling in C++ Techniques

While the basic try-catch mechanism is powerful, C++ offers additional tools for more sophisticated exception handling scenarios.

Exception Specifications (Deprecated)

Exception specifications were a way to declare which exceptions a function might throw.

Example:

void myFunction() throw (std::runtime_error, std::out_of_range) {

// ...

}

This example indicates that myFunction might throw either a std::runtime_error or a std::out_of_range exception. However, exception specifications are now deprecated in modern C++. They had some drawbacks, such as potential performance overhead and unexpected program termination if an unlisted exception was thrown.

Nested try-catch Blocks

We can nest try-catch blocks within each other. This is useful if we have code within an exception handler that might itself throw an exception.

Example:

try {

// Code that might throw an exception

} catch (const std::exception& e) {

try {

// Exception handling code that might also throw

} catch (const std::runtime_error& e2) {

// Handle the nested exception

}

}

finally Block (Non-Standard)

Some compilers offer a finally block as an extension. Code within a finally block always executes, regardless of whether an exception was thrown or not. It is useful for cleanup tasks that must happen even if an error occurs. The finally block is not part of standard C++, so its availability and behavior might vary between compilers.

Example:

try {

// ...

} catch (...) {

// ...

} finally {

// Cleanup code (e.g., close files, release resources)

}

Stack Unwinding

When an exception is thrown, the program execution jumps to the nearest suitable catch block. During this process, the function call stack is "unwound." This means:

  1. The current function's execution is stopped.
  2. Objects created within that function are destroyed (their destructors are called).
  3. The program control moves back up the call stack to the function that called the current one.
  4. This process continues until a matching catch block is found or the program terminates if no suitable handler is found.

If we are not careful, stack unwinding can lead to resource leaks. Suppose a function opens a file and throws an exception before closing it. If we do not have proper exception handling, the file might remain open. To prevent this, consider using techniques like RAII (Resource Acquisition Is Initialization) to ensure resources are automatically released when they go out of scope.

Final Tips

In essence, exception handling is like having a safety net for our C++ programs. It allows us to anticipate potential problems, react to them intelligently, and keep your software running smoothly.

If you wish to learn programming languages such as C++, you can check out upGrad’s computer science programs such as the Master’s in Computer Science Program.

Frequently Asked Questions

  1. What is exception handling in C++?

If I were to explain exception handling in C++ in a single sentence, I would define it as a mechanism that allows us to gracefully handle unexpected errors or events during program execution, preventing crashes and enabling recovery strategies.

  1. How does exception handling work in C++?

Exception handling in C++ uses try-catch blocks to monitor for errors, allowing the program to gracefully handle unexpected events without crashing.

  1. What is the syntax for using exception handling in C++?

The syntax involves a try block to enclose code that might throw exceptions, followed by one or more catch blocks to handle specific types of exceptions.

  1. Can I define my own exception types in C++?

Yes, you can create your own custom exception classes by inheriting from standard C++ exception classes.

  1. Why is error handling important?

Error handling is essential to prevent crashes, provide informative feedback, and ensure your program behaves predictably in unexpected situations.

  1. When should I use exception handling in my C++ code?

You should use exception handling in C++ when you anticipate potential errors that could disrupt your program's normal flow, such as invalid input, resource failures, or logic errors.

  1. Is exception handling efficient in C++?

Exception handling in C++ can have a slight performance overhead, but it's generally considered a worthwhile tradeoff for robustness and maintainability in most cases.

  1. How many types of exception handling are there in C++?

There are two main types of exception handling in C++, synchronous exception handling and asynchronous exception handling.

Abhimita Debnath

Abhimita Debnath

Abhimita Debnath is one of the students in UpGrad Big Data Engineering program with BITS Pilani. She's a Senior Software Engineer in Infosys. She…Read More

Need Guidance? We're Here to Help!
form image
+91
*
By clicking, I accept theT&Cand
Privacy Policy
image
Join 10M+ Learners & Transform Your Career
Learn on a personalised AI-powered platform that offers best-in-class content, live sessions & mentorship from leading industry experts.
right-top-arrowleft-top-arrow

upGrad Learner Support

Talk to our experts. We’re available 24/7.

text

Indian Nationals

1800 210 2020

text

Foreign Nationals

+918045604032

Disclaimer

upGrad does not grant credit; credits are granted, accepted or transferred at the sole discretion of the relevant educational institution offering the diploma or degree. We advise you to enquire further regarding the suitability of this program for your academic, professional requirements and job prospects before enr...