Chapter 29: Exceptions

Introduction

Exceptions provide a way to handle errors or exceptional conditions in a program. They allow you to separate error-handling code from regular code, making your programs easier to read and maintain.

Basic Syntax

Throwing and Catching Exceptions

try
{
    // Statements that may throw exceptions you want to handle go here
    throw -1; // here's a trivial throw statement
}
catch (int x)
{
    // Handle an exception of type int here
    std::cerr << "We caught an int exception with value " << x << '\n';
}

Catch-All Handler

catch (...) // catch-all handler
{
    std::cerr << "We caught an exception of unknown type";
}

Custom Exception Classes

You can create classes primarily used to handle exceptions.

class ArrayException
{
private:
    std::string m_error;

public:
    ArrayException(std::string_view error)
        : m_error{ error }
    {
    }

    const std::string& getError() const { return m_error; }
};

Handlers for derived exception classes should be listed before those for base classes.

Standard Library Exceptions

std::exception (defined in the <exception> header) is a small interface class designed to serve as a base class to any exception thrown by the C++ standard library.

catch (const std::exception& exception)
{
    std::cerr << "Standard exception: " << exception.what() << '\n';
}

Throwing a standard exception:

throw std::runtime_error("Invalid index");

noexcept Specifier

The noexcept specifier means the function promises not to throw exceptions itself.

void doSomething() noexcept; // this function is specified as non-throwing

Function Try Blocks

Use function try blocks when you need a constructor to handle an exception thrown in the member initializer list.

class B : public A
{
public:
    B(int x) try : A{x} // note addition of try keyword here
    {
    }
    catch (...) // note this is at the same level of indentation as the function itself
    {
        // Exceptions from member initializer list or constructor body are caught here
        std::cerr << "Exception caught\n";
        throw; // rethrow the existing exception
    }
};

Dynamic Memory Management in Try Blocks

Ways to ensure that dynamic memory in try blocks will be deallocated if an error occurs:

  1. Create a pointer before the try block.
  2. Use std::unique_ptr.
auto* john { new Person("John", 18, PERSON_MALE) };
std::unique_ptr<Person> upJohn { john }; // upJohn now owns john

Destructors and Exceptions

Destructors should not throw exceptions.

When to Use Exceptions

Exceptions are best used when all of the following are true:

noexcept in Function Declarations

noexcept(true) // true: not throwing, false: potentially throwing noexcept(expression) // check if it's potentially throwing

std::move_if_noexcept

std::move_if_noexcept will return a movable r-value if the object has a noexcept move constructor, otherwise it will return a copyable l-value.

Conclusion

Exceptions provide a way to handle errors or exceptional conditions in a program. They allow you to separate error-handling code from regular code, making your programs easier to read and maintain.