EXCEPTION HANDLING:


Occasionally, functions don't perform as expected, even ones we've written ourselves. Many novice programmers assume their code will always run smoothly, but seasoned developers understand that unexpected issues can arise. For instance, when attempting to read a file from disk, it may not exist, or when writing to a disk, it could be full or unformatted. Similarly, if a program prompts user input, users might provide invalid data.

These errors that occur during the execution of Object-Oriented Programming are termed "Exceptions," and the techniques used to manage them fall under the umbrella of Exception Handling.

Exception handling in c++

C++ Exception handling relies on three keywords: try, catch, and throw. The fundamental idea is to encapsulate code that might encounter issues within a try block. If an exception arises, the throw keyword is used to signal it, and then one or more catch blocks can handle the exception appropriately. A simple illustration of this concept is a program that divides two numbers:

C++ Copy to Clipboard  
#include<iostream>
using namespace std;
 
int divide(int a, int b) {
    return a / b;
}
 
int main() {
    int x, y;
    cout << "Enter two numbers: " << endl;
    cin >> x >> y;
 
    cout << divide(x, y);
    return 0;
}

This code works fine under normal circumstances. However, what if the user inputs "0" as the divisor? Since dividing by zero is impossible, the program would likely encounter a runtime error or exception. In a real-world scenario, such errors could confuse users and developers alike. To address this, we can employ try, catch, and throw.

A tryblock identifies a section of code where specific exceptions might occur. It's followed by one or more catchblocks. The code within the try block is what we want to monitor for errors. This section can range from a few statements within a function to encapsulating the entire main()function, effectively monitoring the entire program.

When a problem arises during execution, a program throws an exception using the throwkeyword. The general form is throw exception;. If this exception needs to be caught, it must be executed within a tryblock or any function called from within the tryblock.

An exception is caught by a program using an exception handler (catchblock) where we want to manage the problem. The catchkeyword signifies catching an exception. A tryblock can have multiple catch statements associated with it. The type of the exception determines which catchblock is utilized.

If an exception is thrown but no applicable catch statement exists, the program will terminate abnormally. Hence, it's crucial to catch all potential exceptions.

The syntax for tryand catchblocks is as follows:

try
{ 
   // Code inside the try block
   throw exception;
}
 
catch(type exception)
{
   // Code to handle the exception
}

The code within the try block executes normally. If an exception occurs, the throw keyword is used within the try block to raise it. The catch block then handles the exception, receiving the exception passed by throw.

Consider this example:

EXCEPTION HANDLING - C++ Copy to Clipboard  
#include<iostream>
using namespace std;
 
int main()
{
   int x;
   cout << "Enter the value of x: ";
   cin >> x;
 
   cout << "Before try: " << endl;
   try
   {
      cout << "Inside try: " << endl;
      if(x < 0)
      {
         throw x;
      }
   }
   catch (int x)
   {
     cout << "Exception Caught: " << endl; 
   }
 
   cout << "After catch: " << endl; 
   return 0;
}

C++ provides a set of standard exceptions defined in <exception>that we can use in our programs. Here's an overview of these exceptions:

EXCEPTIONDESCRIPTION
bad_alloc:Indicates failure to allocate storage during dynamic memory allocation via the new operator.
bad_cast:Thrown when a dynamic_cast1to a reference type fails runtime check due to unrelated types.
bad_typeid:Thrown when applying the typeid2operator to a dereferenced null pointer of a polymorphic type.
bad_exception:Represents unexpected exceptions in C++ programs, thrown by the runtime.
logic_error:Reports errors from faulty logic within the program, such as violating logical preconditions.
domain_error:Indicates errors resulting from inputs outside the defined domain or operations.
invalid_argument:Reports errors due to unacceptable argument values.
length_error:Signifies attempts to exceed length limits defined for an object.
out_of_range:Indicates attempts to access elements outside a defined range, like with the at method.
runtime_error:Reports errors beyond the program's scope that cannot be predicted easily.
overflow_error:Used to report arithmetic overflow errors where the result is too large.
range_error:Signals errors where the result of a computation cannot be represented by the destination type.
underflow_error:Reports arithmetic underflow errors where the result is a subnormal floating-point value.

We can define custom exceptions by inheriting and overriding functionality from the exceptionclass. Below is an example:

DEFINE NEW EXCEPTIONS - C++ Copy to Clipboard  
#include <iostream>
#include <exception> 
using namespace std;
 
struct MyException : public exception 
{
   const char * what () const throw () 
   {
      return "C++ Exception";
   }
};
 
int main() 
{
   try 
   {
      throw MyException();
   } 
   catch(MyException& e) 
   {
      cout << "MyException caught" << endl;
      cout << e.what() << endl;
   } 
   catch(exception& e) 
   {
      // Other errors
   }
 
   return 0;
}

Here, what()is a public method provided by the exceptionclass and has been overridden by all child exception classes to return the cause of an exception.