Introduction to Errors and Exceptions Handling in C++

Introduction to Errors and Exceptions Handling in C++

Software systems do not always behave perfectly during execution. A banking application may lose connection to the server, a file may fail to open, or a user may enter invalid data. These situations occur while the program is running and can interrupt the normal flow of execution. Such abnormal runtime situations are called exceptions.

If these situations are not handled properly, the program may terminate unexpectedly. Modern software systems therefore use exception handling mechanisms to detect errors and respond safely instead of crashing completely.

Consider an ATM system. If the machine temporarily loses communication with the bank server, the system does not suddenly shut down. Instead, it displays a message such as:

Unable to process your request.
Please try again later.

This controlled response is an example of exception handling.

Why Traditional Error Handling Was Not Enough

Before exception handling was introduced, programmers usually handled errors using return values or status codes.

Consider the following example:

#include <iostream>
using namespace std;

int divide(int a, int b)
{
    if(b == 0)
        return -1;

    return a / b;
}

int main()
{
    cout << divide(10, 0);

    return 0;
}

In this program, the function returns -1 if division by zero occurs. Although this approach appears simple, it creates several problems. A programmer may forget to check the returned value, or -1 itself may actually be a valid answer in another situation. As software systems grow larger, mixing error-handling logic with normal program logic makes programs difficult to understand and maintain.

To solve this issue, C++ introduced exception handling.

Basic Idea of Exception Handling

Exception handling in C++ is based on three important keywords:

  • try
  • throw
  • catch

The idea is straightforward. The risky code is placed inside a try block. If an abnormal condition occurs, the program generates an exception using throw. The exception is then received and handled inside a catch block.

The general structure looks like this:

try
{
    // risky code
}
catch(...)
{
    // handling code
}

The try block contains the statements that may generate problems. The catch block handles the exception so that the program can continue safely.

First Example — Division by Zero

Let us understand the complete process through a simple program.

#include <iostream>
using namespace std;

int main()
{
    int a, b;

    cout << "Enter two numbers: ";
    cin >> a >> b;

    try
    {
        if(b == 0)
            throw "Division by zero is not allowed.";

        cout << "Result = " << a / b << endl;
    }

    catch(const char* msg)
    {
        cout << "Error: " << msg << endl;
    }

    return 0;
}

In this program, the user enters two numbers. Before division is performed, the program checks whether the denominator is zero. If the denominator becomes zero, the program uses the throw statement to generate an exception.

As soon as the exception is thrown, the normal execution of the try block stops immediately. Control jumps directly to the catch block, where the error message is displayed.

Understanding Program Flow

Students often misunderstand how control moves during exception handling. It is therefore important to understand the execution flow carefully.

Program starts
      ↓
try block begins
      ↓
error occurs
      ↓
throw statement executes
      ↓
remaining try block skipped
      ↓
catch block executes
      ↓
program continues safely

Once an exception is thrown, the remaining statements inside the try block are ignored. The program immediately searches for a matching catch block.

Throwing Different Types of Exceptions

In C++, exceptions are actually data values or objects. Different types of data can be thrown depending on the situation.

throw 10;
throw 3.14;
throw "Invalid Input";

Because exceptions can have different data types, C++ allows multiple catch blocks to handle different situations.

Multiple catch Blocks

Consider the following example:

#include <iostream>
using namespace std;

int main()
{
    try
    {
        throw 10;
    }

    catch(int x)
    {
        cout << "Integer exception caught." << endl;
    }

    catch(double y)
    {
        cout << "Double exception caught." << endl;
    }

    catch(...)
    {
        cout << "Unknown exception caught." << endl;
    }

    return 0;
}

Here, the program throws an integer value. The catch(int x) block handles the exception because its type matches the thrown value.

The catch(...) block is called the catch-all handler. It handles any exception type that does not match earlier catch blocks. For this reason, it should always appear at the end.

Exception Handling with Functions

In real software systems, exceptions usually occur inside functions rather than directly inside main().

Consider the following example:

#include <iostream>
using namespace std;

void divide(int a, int b)
{
    if(b == 0)
        throw "Division by zero.";

    cout << "Result = " << a / b << endl;
}

int main()
{
    try
    {
        divide(10, 0);
    }

    catch(const char* msg)
    {
        cout << msg << endl;
    }

    return 0;
}

The function divide() throws an exception when division by zero occurs. The exception is not handled inside the function itself. Instead, it is handled in main().

This demonstrates an important concept called exception propagation.

Exception Propagation

When a function cannot handle an exception, the exception automatically moves upward to the calling function. This process is known as exception propagation.

Consider the following execution chain:

main()
 ↓
login()
 ↓
database()
 ↓
file()

If file() throws an exception and does not handle it, the exception travels upward through database(), then login(), until some function finally catches it.

The following example demonstrates this process clearly.

#include <iostream>
using namespace std;

void level3()
{
    throw "File not found!";
}

void level2()
{
    level3();
}

void level1()
{
    level2();
}

int main()
{
    try
    {
        level1();
    }

    catch(const char* msg)
    {
        cout << "Caught in main: " << msg << endl;
    }

    return 0;
}

The exception originates in level3(). Since none of the intermediate functions handle it, the exception eventually reaches main().

Standard Exception Classes in C++

C++ already provides many built-in exception classes through the Standard Library. These classes help programmers handle common runtime problems in a professional way.

Some commonly used standard exceptions are:

runtime_error
out_of_range
invalid_argument
bad_alloc

These classes are derived from the base class std::exception.

Using Standard Exceptions

Consider the following example using out_of_range.

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    vector<int> nums = {10, 20, 30};

    try
    {
        cout << nums.at(5);
    }

    catch(const out_of_range &e)
    {
        cout << "Exception: " << e.what() << endl;
    }

    return 0;
}

The function vector.at() automatically throws an exception if the index is invalid. The what() function returns a descriptive error message associated with the exception object.

This approach is much cleaner and more professional than manually checking every condition.

Custom Exceptions

Large software systems often require application-specific exceptions. For example, a banking system may need exceptions such as:

LowBalanceException
InvalidPINException
AccountBlockedException

These custom exceptions make programs easier to understand and maintain.

Creating a Simple Custom Exception

A custom exception can be created using a normal class.

#include <iostream>
using namespace std;

class InvalidAge {};

int main()
{
    int age;

    cout << "Enter age: ";
    cin >> age;

    try
    {
        if(age < 18 || age > 60)
            throw InvalidAge();

        cout << "Valid age." << endl;
    }

    catch(InvalidAge)
    {
        cout << "Invalid age entered." << endl;
    }

    return 0;
}

Here, the program throws an object of class InvalidAge whenever the entered age falls outside the valid range.

Professional Custom Exceptions

In professional C++ programming, custom exceptions are usually derived from std::exception.

#include <iostream>
#include <exception>
using namespace std;

class InvalidAge : public exception
{
public:

    const char* what() const noexcept override
    {
        return "Age must be between 18 and 60.";
    }
};

int main()
{
    int age;

    cout << "Enter age: ";
    cin >> age;

    try
    {
        if(age < 18 || age > 60)
            throw InvalidAge();

        cout << "Age accepted." << endl;
    }

    catch(const InvalidAge &e)
    {
        cout << e.what() << endl;
    }

    return 0;
}

The what() function provides a meaningful message describing the problem.

Resource Management and Exception Safety

One of the biggest reasons for using exception handling is protecting system resources such as files, memory, database connections, and network sockets.

Consider a situation where a program opens a file and then an exception occurs before the file is closed. Without proper handling, the file may remain open and cause resource leaks.

C++ solves this problem using destructors and RAII (Resource Acquisition Is Initialization).

File Handling Example

#include <iostream>
#include <fstream>
using namespace std;

class FileReader
{
    ifstream file;

public:

    FileReader(const string &name)
    {
        file.open(name);

        if(!file)
            throw "Unable to open file.";
    }

    ~FileReader()
    {
        cout << "Closing file..." << endl;

        if(file.is_open())
            file.close();
    }

    void display()
    {
        string line;

        while(getline(file, line))
            cout << line << endl;
    }
};

int main()
{
    try
    {
        FileReader fr("data.txt");
        fr.display();
    }

    catch(const char* msg)
    {
        cout << msg << endl;
    }

    return 0;
}

Even if an exception occurs, the destructor executes automatically and safely closes the file.

Smart Pointers and Exception Safety

Modern C++ provides smart pointers that automatically manage memory.

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    try
    {
        unique_ptr<int> ptr(new int(10));

        cout << "Value: " << *ptr << endl;

        throw "Some error occurred.";
    }

    catch(const char* msg)
    {
        cout << msg << endl;
    }

    return 0;
}

Even though an exception occurs, the smart pointer automatically releases the allocated memory. This prevents memory leaks and improves program safety.

Best Practices

Exception handling should be used carefully and professionally. Exceptions should represent abnormal situations rather than normal program logic. Meaningful error messages should always be provided so users and developers can easily understand the problem.

It is also recommended to catch exceptions by reference because it improves efficiency and avoids unnecessary copying.

Empty catch blocks should generally be avoided because silently ignoring exceptions can make debugging extremely difficult.

Leave a Reply

Your email address will not be published. Required fields are marked *