CPP Programming Standard Exceptions and Custom Exceptions Step by step Implementation and Top 10 Questions and Answers
 .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    Last Update: April 01, 2025      21 mins read      Difficulty-Level: beginner

CPP Programming: Standard Exceptions and Custom Exceptions

Introduction to Exceptions

In C++ programming, exceptions are a powerful tool used for error handling. They provide a way to separate regular code execution from error-handling code, making the program more robust and maintainable. C++ supports both standard and custom exceptions. Standard exceptions are part of the C++ Standard Library, while custom exceptions allow programmers to define their own exception types tailored to specific needs.

Standard Exceptions

The C++ Standard Library offers a comprehensive hierarchy of standard exceptions, which are defined in the header <stdexcept>. The primary base class for most standard exceptions is std::exception. Here are some commonly used standard exceptions:

  1. std::exception

    • This is the base class for all standard exceptions and contains a virtual method what() that returns an explanatory string.
  2. std::logic_error

    • Represents errors in logical reasoning of the programmer, like violations of preconditions or class invariants.
    • Subclasses include:
      • std::invalid_argument: Used when invalid arguments are passed to a function.
      • std::domain_error: Used when a mathematical function is called with arguments outside their valid range.
      • std::length_error: Used when an object is constructed or resized with an inappropriate length.
      • std::out_of_range: Used when an attempt is made to access an element out of the bounds of a container.
  3. std::runtime_error

    • Represents errors occurring during runtime, such as memory allocation failures or input/output errors.
    • Subclasses include:
      • std::range_error: Similar to std::out_of_range.
      • std::overflow_error: Used when an arithmetic operation results in an overflow error.
      • std::underflow_error: Used when an arithmetic operation results in an underflow error.
      • std::runtime_error: General-purpose runtime errors.
      • std::system_error: Used for system errors, often encapsulating error codes.
  4. std::bad_alloc

    • Thrown by memory allocation functions, such as operator new.
  5. std::bad_cast

    • Raised by a failed dynamic_cast on a reference type.
  6. std::bad_typeid

    • Thrown when typeid is applied to unaligned storage.

Throwing and Catching Standard Exceptions

To use standard exceptions, you can throw an instance of an exception class using the throw keyword and catch it using a try-catch block.

#include <iostream>
#include <stdexcept>

void riskyFunction() {
    if (true) { // Simulate some condition
        throw std::out_of_range("Index out of range");
    }
}

int main() {
    try {
        riskyFunction();
    } catch (const std::exception& e) {
        std::cerr << "Standard exception caught: " << e.what() << std::endl;
    }
    return 0;
}

Explanation:

  • riskyFunction() simulates a situation where an std::out_of_range exception is thrown.
  • In main(), we wrap the call to riskyFunction() in a try block.
  • If an exception is thrown, the catch block captures it. The const std::exception& e parameter allows us to handle all standard exceptions, and e.what() provides a description of the exception.

Custom Exceptions

While standard exceptions cover many common scenarios, you may need to create custom exceptions to handle application-specific errors. To create a custom exception, derive a new class from std::exception and override its what() method.

Example: Custom Exception Implementation

#include <iostream>
#include <stdexcept>
#include <string>

class MyCustomException : public std::exception {
private:
    std::string message;

public:
    explicit MyCustomException(const std::string& msg) : message(msg) {}

    const char* what() const noexcept override {
        return message.c_str();
    }
};

void riskyApplicationFunction() {
    throw MyCustomException("Custom error occurred in application function.");
}

int main() {
    try {
        riskyApplicationFunction();
    } catch (const MyCustomException& e) {
        std::cerr << "Custom exception caught: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Standard exception caught: " << e.what() << std::endl;
    }
    return 0;
}

Explanation:

  • MyCustomException is a custom exception class derived from std::exception. It takes a std::string message in its constructor and overrides the what() method to return this message.
  • riskyApplicationFunction() throws an instance of MyCustomException.
  • In main(), the custom exception is caught by catching const MyCustomException&, and other standard exceptions are caught by catching const std::exception&.

Benefits of Using Exceptions

  1. Separation of Concerns: Exceptions allow the separation of error-handling code from the main program flow, promoting cleaner and more organized code.

  2. Graceful Degradation: By catching exceptions, the program can attempt to recover gracefully from errors without terminating abruptly.

  3. Reusability: Standard exceptions are reusable, and custom exceptions can be defined to fit the needs of any application.

  4. Consistency: A consistent mechanism for reporting errors across different modules or layers of the application.

Key Points

  • Standard Exceptions: Part of the C++ Standard Library, providing a wide range of predefined exceptions for various error conditions.
  • Custom Exceptions: Allow users to define exceptions specific to their program’s domain.
  • Exception Hierarchy: Exceptions inherit from std::exception, enabling polymorphic handling.
  • try-catch Blocks: Provide the mechanism for throwing and catching exceptions, with try blocks encapsulating potential error sources and catch blocks handling these errors.

By understanding how to effectively use both standard and custom exceptions, C++ developers can create software that not only performs tasks correctly but also handles errors gracefully, leading to more reliable and user-friendly applications.




CPP Programming Standard Exceptions and Custom Exceptions: A Step-by-Step Guide

Introduction

In C++ programming, handling exceptions is a critical aspect that ensures the program remains robust and continues running smoothly even when errors occur. Standard exceptions provided by the C++ Standard Library handle common error cases, while custom exceptions allow programmers to define specific error conditions tailored to their application's needs. This guide will walk you through understanding, setting up, and implementing both standard and custom exceptions in a C++ program step-by-step.

Prerequisites

Before we dive into exceptions, ensure you have a basic understanding of C++ programming concepts such as functions, classes, and control structures. Additionally, make sure you have a C++ compiler installed on your system (e.g., GCC, Clang, or MSVC).

Setting Up the Environment

To start, prepare a development environment where you can write, compile, and run C++ code. Common choices include:

  • Code::Blocks
  • Visual Studio (VS Code with C++ extensions)
  • CLion
  • Terminal with GCC or Clang

Let’s assume you have a basic set-up with GCC. Open a terminal or command prompt and navigate to a directory where you want to create your project.

Step-by-Step Guide

Step 1: Understanding Standard Exceptions

C++ provides a set of standard exceptions found in the <stdexcept> header. These exceptions cover common error scenarios such as invalid arguments, runtime issues, and logical errors. Here are some commonly used standard exceptions:

  • std::exception: Base class for all standard exceptions.
  • std::runtime_error: Runtime errors.
  • std::logic_error: Logic errors in the program.
  • std::invalid_argument: Invalid arguments to functions.
  • std::out_of_range: Operations that attempt to access an element out of range.
  • std::bad_alloc: Thrown by new on allocation failure.
Step 2: Implementing Standard Exceptions

Let’s create a simple C++ program that demonstrates the usage of a standard exception (std::out_of_range) and a try-catch block.

#include <iostream>
#include <vector>
#include <stdexcept>

int main() {
    try {
        std::vector<int> numbers = {1, 2, 3};

        std::cout << "Attempting to access element at index 5..." << std::endl;
        int value = numbers.at(5);  // This will throw std::out_of_range

        std::cout << "Successfully accessed element: " << value << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "Caught an out_of_range exception: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Caught a standard exception: " << e.what() << std::endl;
    } catch (...) {
        std::cerr << "Caught an unknown exception" << std::endl;
    }

    return 0;
}

Explanation:

  • Try Block: Code that might throw an exception is placed inside the try block.
  • Catch Block: Handles exceptions. Multiple catch blocks can be used to handle different types of exceptions.
  • std::out_of_range: Thrown by std::vector::at() when an invalid index is accessed.
  • std::exception: Catches any exception derived from std::exception.
  • ...: Catches any exception not previously caught.

Compile and Run:

g++ -o std_exception_demo std_exception_demo.cpp
./std_exception_demo

Output:

Attempting to access element at index 5...
Caught an out_of_range exception: vector::_M_range_check: __n (which is 5) >= this->size() (which is 3)
Step 3: Understanding and Implementing Custom Exceptions

Custom exceptions are derived from std::exception and are used to handle application-specific error conditions. Let's create a custom exception to handle invalid user input:

#include <iostream>
#include <stdexcept>
#include <string>

// Custom exception class
class InvalidInputException : public std::exception {
public:
    InvalidInputException(const std::string& message) : message_(message) {}

    const char* what() const noexcept override {
        return message_.c_str();
    }

private:
    std::string message_;
};

int main() {
    int age;

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

    try {
        if (age < 0) {
            throw InvalidInputException("Invalid age: age cannot be negative!");
        } else if (age > 120) {
            throw InvalidInputException("Invalid age: age cannot be greater than 120!");
        }

        std::cout << "Age entered: " << age << std::endl;
    } catch (const InvalidInputException& e) {
        std::cerr << "Caught an InvalidInputException: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Caught a standard exception: " << e.what() << std::endl;
    } catch (...) {
        std::cerr << "Caught an unknown exception" << std::endl;
    }

    return 0;
}

Explanation:

  • Custom Exception Class: Define a class InvalidInputException derived from std::exception.
  • Constructor: Takes a custom error message.
  • what() Method: Returns the error message as a const char*.
  • Throwing Exception: throw InvalidInputException("Error message") raises the custom exception.
  • Catch Block: Handles the custom exception.

Compile and Run:

g++ -o custom_exception_demo custom_exception_demo.cpp
./custom_exception_demo

Sample Input and Output:

Enter your age: -5
Caught an InvalidInputException: Invalid age: age cannot be negative!
Enter your age: 130
Caught an InvalidInputException: Invalid age: age cannot be greater than 120!
Enter your age: 25
Age entered: 25

Data Flow and Exception Handling

The flow of data and exception handling in the application is as follows:

  1. User Input: The user is prompted to enter their age.
  2. Validation: The age is validated using conditional statements.
  3. Exception Handling:
    • If the age is negative, an InvalidInputException is thrown with a specific error message.
    • If the age is greater than 120, another InvalidInputException is thrown.
    • If the age is valid, it is printed to the console.
  4. Try-Catch Block: The try block attempts to execute the code that may throw an exception, while the catch block handles the exception by displaying an appropriate error message.

Conclusion

Mastering exceptions in C++—both standard and custom—is essential for developing robust and error-free applications. By following the steps provided, you should now understand how to implement and handle exceptions in your C++ programs. Experimenting with different types of exceptions and error conditions will strengthen your understanding and skills in handling exceptions effectively. Happy coding!




Top 10 Questions and Answers on C++ Programming: Standard Exceptions and Custom Exceptions

1. What are exceptions in C++?

Answer: Exceptions in C++ are a feature that allows the programmer to handle unexpected or exceptional situations more gracefully. When an error occurs, an exception can be thrown by using the throw keyword, and it can be caught and handled by the program's logic using try, catch, and optionally finally blocks (though finally is not directly available in C++, you can mimic its behavior using RAII or other constructs).

2. What are standard exceptions in C++?

Answer: Standard exceptions in C++ are part of the standard library and help manage common errors encountered during program execution. They are defined in the <stdexcept> and <exception> headers.

  • std::exception: The base class for all standard exceptions.
  • std::logic_error: Thrown for problems resulting from faulty logic within a program.
  • std::runtime_error: Thrown for problems detected during execution, typically indicating a failure in runtime facilities.
  • std::bad_alloc: Thrown when a memory allocation fails.
  • std::out_of_range: Thrown to report an argument value is outside a legitimate range.
  • std::overflow_error: Thrown for arithmetic overflow.
  • std::underflow_error: Thrown for arithmetic underflow.
  • std::length_error: Thrown when an object exceeds its maximum allowable size.
  • std::invalid_argument: Thrown for invalid arguments to functions.
  • std::domain_error: Thrown for domain errors, e.g., log(-1).
  • std::range_error: Thrown when an argument’s numeric value does not fall within the range of values expected for a function, but it's different from std::out_of_range and generally used in other mathematical contexts.
  • std::bad_cast: Thrown for bad dynamic casts.
  • std::bad_typeid: Thrown when typeid is applied to a null pointer of type std::type_info.

3. How do you throw and catch exceptions in C++?

Answer: In C++, exceptions are thrown using the throw keyword and caught using a combination of try and catch blocks:

#include <iostream>
#include <stdexcept>

void riskyFunction() {
    throw std::invalid_argument("Argument is invalid");
}

int main() {
    try {
        riskyFunction();
    } catch (const std::invalid_argument& e) {
        std::cout << "Caught an invalid_argument: " << e.what() << '\n';
    } catch (const std::exception& e) {
        std::cout << "Caught a generic exception: " << e.what() << '\n';
    } catch (...) {  // catches all exceptions not caught by above handlers
        std::cout << "Caught an unknown exception\n";
    }
    return 0;
}

In this example, if riskyFunction() throws a std::invalid_argument, it is caught by the first catch block. Otherwise, it is caught by more generic catch blocks, including one that catches any exceptions (catch(...)).

4. Explain std::exception and inheritance in C++ exceptions.

Answer: std::exception is a base class for all standard exceptions in C++. Most standard exception classes inherit from std::exception. This inheritance makes it possible to catch most exception types with a single catch block designed to handle std::exception instances:

try {
    // Some risky code
} catch (const std::exception& e) {
    std::cout << "Error occurred: " << e.what() << '\n';
}

In this context, the what() method provides a message associated with the exception. For instance, when catching std::invalid_argument or any other standard exception derived from std::exception, you can call e.what() to get the detailed error message.

5. What is the role of the 'what()' function in exceptions?

Answer: The what() function in C++ exceptions returns a C-string describing the nature of the error. It is part of the base class std::exception and is often overridden in derived classes to provide specific error messages:

class MyException : public std::exception {
public:
    const char* what() const noexcept override {
        return "This is a custom exception message";
    }
};

int main() {
    try {
        throw MyException();
    } catch (const std::exception& e) {
        std::cout << e.what() << '\n';  // Outputs: This is a custom exception message
    }
}

The what() function helps developers understand what went wrong during program execution by providing relevant information about the exception.

6. How do you create custom exceptions in C++?

Answer: Creating custom exceptions in C++ involves deriving a class from std::exception (or any of its subclasses) and overriding the what() method:

#include <iostream>
#include <exception>

class MyCustomException : public std::runtime_error {
public:
    MyCustomException(const std::string& msg) : std::runtime_error(msg) {}

    const char* what() const noexcept override {
        return std::runtime_error::what();  // Return the custom message provided during construction
    }
};

void riskyOperation(int i) {
    if (i < 0) {
        throw MyCustomException("Negative value passed to riskyOperation");
    }
}

int main() {
    try {
        riskyOperation(-1);
    } catch (const MyCustomException& e) {
        std::cout << "Caught MyCustomException: " << e.what() << '\n';
    } catch (const std::exception& e) {
        std::cout << "Caught std::exception: " << e.what() << '\n';
    }
    return 0;
}

In this example, MyCustomException inherits from std::runtime_error, which itself inherits from std::exception. Overriding what() lets you specify exactly what message should accompany your custom exceptions.

7. Why should you use custom exceptions?

Answer: Custom exceptions allow for better error handling and make the code more understandable and maintainable. Here are key reasons:

  • Specificity: Custom exceptions provide more specific error messages than using standard exceptions alone. This clarity aids debugging.
  • Differentiation: Allows distinguishing between different exceptional conditions using distinct exception classes.
  • Structured Handling: Helps structure and organize error handling logic, making it easier to identify which parts of the code might lead to particular errors.
  • Readability: Custom exception names explain their purpose and context clearly, improving code readability.

For example, a banking application might have custom exceptions like InsufficientFundsException, InvalidTransactionIDException, etc., helping to manage different types of errors in a precise manner.

8. Can you catch multiple exceptions in a single catch block?

Answer: No, a single catch block can only catch one type of exception at a time. However, you can catch multiple types of exceptions that derive from the same base class using polymorphism or catch them all in a generic way:

catch (const std::runtime_error& e) {
    // Handles runtime errors and all their derivatives (like InsufficientFunds)
}

Or, using a generic catch block:

catch (const std::exception& e) {
    // Handles all standard exceptions and possibly custom exceptions inheriting from std::exception
}

You can also chain multiple catch blocks that each catch a different type:

catch (const std::invalid_argument& e) {
    // Handle invalid argument exception
} catch (const std::out_of_range& e) {
    // Handle out-of-range exception
} catch (...) {
    // Handle all other exceptions
}

9. What is the significance of noexcept in C++ exceptions?

Answer: The noexcept specifier indicates that a function will not throw any exceptions. This is significant for performance, as it allows the compiler to make certain optimizations. Additionally, certain constructs in C++, such as move constructors and move assignment operators, require functions to be declared noexcept in order to participate in certain operations:

void safeFunction() noexcept {
    // This function promises not to throw any exceptions
}

If a noexcept function actually throws an exception, the program calls std::terminate(), usually aborting or crashing the application.

Using noexcept judiciously can make the code more robust, especially in critical sections where exceptions are undesirable:

std::vector<int> data;

data.push_back(1);  // push_back is noexcept in some cases

10. How do you handle exceptions across multiple layers in a C++ program?

Answer: Handling exceptions across multiple layers of a C++ program involves carefully propagating exceptions from lower layers to higher layers where they can be appropriately managed. This is often achieved using a combination of try, catch, and rethrow mechanisms.

When an exception is thrown, it propagates up the call stack until it finds a catch block capable of handling it. If no suitable handler is found, the program will terminate. To manage exceptions better across layers, you might:

  • Catch Specific Exceptions: Handle only the exceptions that you are prepared to deal with at each layer.
  • Rethrow Exceptions: Use the throw; statement to rethrow an exception so that it can be handled at a higher level without losing its type or data.
  • Handle at Higher Levels: Use generic catch blocks at higher levels (like main) when you want to perform cleanup or logging, ensuring that exceptions don’t propagate beyond controllable boundaries.

Example demonstrating exception handling across layers:

#include <iostream>
#include <stdexcept>

class DatabaseError : public std::runtime_error {
public:
    DatabaseError(const std::string& msg) : std::runtime_error(msg) {}
};

class BusinessLogicError : public std::runtime_error {
public:
    BusinessLogicError(const std::string& msg) : std::runtime_error(msg) {}
};

void accessDatabase() {
    throw DatabaseError("Failed to connect to database");
}

void processPayment(int sum) {
    try {
        accessDatabase();
        // Perform payment processing...
        if (sum <= 0) {
            throw BusinessLogicError("Invalid payment amount");
        }
    } catch (const DatabaseError& dbErr) {
        std::cerr << "DatabaseError: " << dbErr.what() << '\n';
        throw;  // Rethrow to be handled at a higher level
    }
}

int main() {
    try {
        processPayment(-10);  // Invalid payment amount
    } catch (const DatabaseError& dbErr) {
        std::cerr << "Database issue encountered\n";
        // Handle database failures
    } catch (const BusinessLogicError& blErr) {
        std::cerr << "BusinessLogicError: " << blErr.what() << '\n';
        // Handle business logic issues
    } catch (const std::exception& e) {
        std::cerr << "General Exception: " << e.what() << '\n';
        // Handle other exceptions
    } catch (...) {
        std::cerr << "Unknown exception caught\n";
        // Handle unexpected issues
    }
    return 0;
}

Here, an exception thrown in accessDatabase() is caught and then rethrown by processPayment(). It is finally handled in main(), which makes the code more modular and maintainable, allowing different parts of the application to handle exceptions independently.


By understanding and effectively using both standard and custom exceptions, C++ developers can create programs that are robust, predictable, and easier to debug. Exceptions enable a structured approach to error management, enhancing overall software quality and reliability.