Blog |

Throwing Exceptions in C++

Throwing Exceptions in C++
Table of Contents

Imagine spending months developing a C++ application, only to have users report that it crashes whenever they enter unexpected input or when network connections fail. This common scenario happens when programs lack proper error handling.

The good news is that C++ provides a built-in mechanism called exceptions that helps your code anticipate and respond to problems rather than simply crashing.

In this comprehensive guide, you'll learn how to use exceptions to create resilient applications that can detect errors, communicate what went wrong, and recover gracefully - skills that separate amateur code from professional-grade applications.

Why Use Exceptions in C++?

Exceptions provide a way to react to exceptional circumstances in programs by transferring control to special functions called handlers.

Throwing exceptions are preferred in modern C++ over traditional error handling for the following reasons:

  • Automatic resource cleanup — C++ destroys all objects in scope after an exception occurs, preventing resource leaks
  • Separation of concerns — Exceptions separate error-handling logic from the main algorithm, improving code readability
  • Flexible error handling — Errors can be handled at the appropriate level of abstraction, not necessarily where they occur
  • Type-based categorization — C++ error types can be organized in hierarchies, making it easier to handle related errors consistently
  • Cleaner code — They eliminate the need to check return values after every function call

These advantages make exceptions particularly valuable in large-scale projects where code clarity and maintainability are essential.

C++ try catch and throw

Exception handling in C++ is done using three keywords: try, catch and throw.

To catch exceptions, a portion of code is placed under exception inspection. This is done by enclosing this portion of code in a try block. When an exception occurs within the try block, control is transferred to the exception handler. If no exception is thrown, the code continues normally and the handlers are ignored.

An exception in C++ is thrown by using the throw keyword from inside the try block. The throw keyword allows the programmer to define custom exceptions.

Exception handlers in C++ are declared with the catch keyword, which is placed immediately after the try block. Multiple handlers (catch expressions) can be chained - each one with a different exception type. Only the handler whose argument type matches the exception type in the throw statement is executed.

C++ does not require a finally block to make sure resources are released if an exception occurs.

C++ Throw Exception Example

The basic syntax for throwing an exception is straightforward:

throw exception_object;

Where exception_object can be any expression, including a function call, that evaluates to an object or a value that can be copied.

The following example shows the syntax for throwing and catching exceptions in C++:

#include <iostream>
#include <stdexcept>

using namespace std;

int AddPositiveIntegers(int a, int b)
{
    if (a < 0 || b < 0)
        throw std::invalid_argument("AddPositiveIntegers arguments must be positive");

    return (a + b);
}

int main()
{
    try
    {
        cout << AddPositiveIntegers(-1, 2); //exception
    }

    catch (std::invalid_argument& e)
    {
        cerr << e.what() << endl;
        return -1;
    }

    return 0;
}

In this C++ throw exception example, the AddPositiveIntegers() function is called from inside the try block in the main() function. The AddPositiveIntegers() expects two integers a and b as arguments, and throws an invalid_argument exception in case any of them are negative.

The std::invalid_argument class is defined in the standard library in the <stdexcept> header file. This class defines types of objects to be thrown as exceptions and reports errors in C++ that occur because of illegal argument values.

The catch block in the main() function catches the invalid_argument exception and handles it.

Exception Handling in C++ Constructors

An exception should be thrown from a C++ constructor whenever an object cannot be properly constructed or initialized. Since there is no way to recover from failed object construction, an exception should be thrown in such cases.

Constructors should also throw C++ exceptions to signal any input parameters received outside of allowed values or range of values.

Since C++ constructors do not have a return type, it is not possible to use return codes. Therefore, the best practice is for constructors to throw an exception to signal failure.

The throw statement can be used to throw an C++ exception and exit the constructor code.

How to Create Custom Exception Classes

While the standard library provides several exception classes, real-world applications often require custom exceptions that carry domain-specific information. Here's how to create a custom exception hierarchy:

#include <stdexcept>
#include <string>

// Base exception for our application
class AppException : public std::runtime_error {
public:
    AppException(const std::string& message) 
        : std::runtime_error(message) {}
};

// More specific exceptions
class DatabaseException : public AppException {
public:
    DatabaseException(const std::string& message, int errorCode) 
        : AppException(message), errorCode_(errorCode) {}

    int getErrorCode() const { return errorCode_; }

private:
    int errorCode_;
};

class NetworkException : public AppException {
public:
    NetworkException(const std::string& message, const std::string& endpoint) 
        : AppException(message), endpoint_(endpoint) {}

    const std::string& getEndpoint() const { return endpoint_; }

private:
    std::string endpoint_;
};

Using these custom exceptions allows you to provide more context about what went wrong:

try {
    // Some code that might throw our custom exceptions
} catch (const DatabaseException& e) {
    std::cerr << "Database error (code " << e.getErrorCode() 
              << "): " << e.what() << std::endl;
} catch (const NetworkException& e) {
    std::cerr << "Network error with endpoint " << e.getEndpoint() 
              << ": " << e.what() << std::endl;
} catch (const AppException& e) {
    std::cerr << "Application error: " << e.what() << std::endl;
} catch (const std::exception& e) {
    std::cerr << "Standard exception: " << e.what() << std::endl;
}

Exception Safety Guarantees

C++ recognizes several levels of exception safety that your code can provide:

  1. Basic guarantee: If an exception is thrown, no resources are leaked and the program remains in a valid but unspecified state.
  2. Strong guarantee: If an exception is thrown, the operation has no effects (the program state is unchanged).
  3. No-throw guarantee: The operation will not throw exceptions. Often marked with noexcept in modern C++.

Here's an example of providing a strong guarantee using the copy-and-swap idiom:

class Resource {
    // Resource class implementation
};

class Widget {
    Resource* resource_;
public:
    // Strong exception guarantee for assignment
    Widget& operator=(const Widget& other) {
        // Create a copy first (might throw)
        Resource* new_resource = new Resource(*other.resource_);

        // Then swap (non-throwing)
        delete resource_;
        resource_ = new_resource;

        return *this;
    }
};

Modern Exception Specifications with noexcept

In modern C++, the noexcept specifier indicates that a function will not throw exceptions:

void function1() noexcept;  // This function promises not to throw
void function2() noexcept(condition);  // Conditionally noexcept

The noexcept specification serves several purposes:

  1. It communicates to other developers which functions might throw
  2. It enables compiler optimizations when the compiler knows exceptions won't be thrown
  3. It allows the Standard Library to provide stronger guarantees and better performance

Best practices for using noexcept:

  • Mark move constructors and move assignment operators as noexcept when appropriate
  • Mark destructors as noexcept (they are by default in C++11 and later)
  • Mark functions noexcept only when you can guarantee they won't throw
  • Consider using conditional noexcept for templates
// Example of conditional noexcept
template <class T>
void swap(T& a, T& b) noexcept(noexcept(T(std::move(a)))) {
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}

Remember that if a function marked with noexcept actually throws an exception, the program will call std::terminate() and immediately shut down. This is more severe than a normal unhandled exception, so use noexcept only when you're certain no exceptions will occur.

Track, Analyze and Manage C++ Errors With Rollbar

Rollbar in action

Managing errors and throwing exceptions in your code is challenging. It can make deploying production code an unnerving experience. Being able to track, analyze, and manage errors in real-time can help you to proceed with more confidence. Rollbar automates error monitoring and triaging, making fixing C++ errors easier than ever.

Sign Up Today!

"Rollbar allows us to go from alerting to impact analysis and resolution in a matter of minutes. Without it we would be flying blind."

Error Monitoring

Start continuously improving your code today.

Get Started Shape