exception_vs_assertion


Introduction

In C++, assertions and exceptions are both mechanisms for handling errors and unexpected conditions, but they serve different purposes and are used in different scenarios. Understanding when to use assertions versus exceptions is crucial for writing robust and maintainable C++ code. This comparison will explore the characteristics, use cases, and best practices for both assertions and exceptions.

Example 1: Basic Usage Comparison

#include <cassert>
#include <iostream>
#include <stdexcept>

// Function using assertion
int divideWithAssert(int a, int b) {
    assert(b != 0 && "Divisor cannot be zero");
    return a / b;
}

// Function using exception
int divideWithException(int a, int b) {
    if (b == 0) {
        throw std::invalid_argument("Divisor cannot be zero");
    }
    return a / b;
}

int main() {
    try {
        std::cout << "Assert: 10 / 2 = " << divideWithAssert(10, 2) << std::endl;
        std::cout << "Exception: 10 / 2 = " << divideWithException(10, 2) << std::endl;

        // Uncomment to see assertion failure
        // std::cout << "Assert: 10 / 0 = " << divideWithAssert(10, 0) << std::endl;

        std::cout << "Exception: 10 / 0 = " << divideWithException(10, 0) << std::endl;
    }
    catch (const std::exception& e) {
        std::cerr << "Exception caught: " << e.what() << std::endl;
    }

    return 0;
}

Explanation

This example demonstrates the basic usage of assertions and exceptions:

Example 2: Assertions for Invariants, Exceptions for Runtime Errors

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

class SafeArray {
private:
    std::vector<int> data;

public:
    void push_back(int value) {
        data.push_back(value);
    }

    int& at(size_t index) {
        assert(index < data.size() && "Index out of bounds (assertion)");
        if (index >= data.size()) {
            throw std::out_of_range("Index out of bounds (exception)");
        }
        return data[index];
    }
};

int main() {
    SafeArray arr;
    arr.push_back(10);
    arr.push_back(20);

    try {
        std::cout << "Element at index 1: " << arr.at(1) << std::endl;
        std::cout << "Element at index 2: " << arr.at(2) << std::endl;
    }
    catch (const std::out_of_range& e) {
        std::cerr << "Exception caught: " << e.what() << std::endl;
    }

    return 0;
}

Explanation

This example illustrates the use of assertions for invariants and exceptions for runtime errors:

Example 3: Performance Considerations

#include <cassert>
#include <iostream>
#include <chrono>
#include <vector>
#include <stdexcept>

void performOperationWithAssert(const std::vector<int>& vec, int index) {
    assert(index < vec.size() && "Index out of bounds");
    std::cout << vec[index] << std::endl;
}

void performOperationWithException(const std::vector<int>& vec, int index) {
    if (index >= vec.size()) {
        throw std::out_of_range("Index out of bounds");
    }
    std::cout << vec[index] << std::endl;
}

int main() {
    std::vector<int> numbers(1000000, 42);
    const int iterations = 1000000;

    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < iterations; ++i) {
        performOperationWithAssert(numbers, i % numbers.size());
    }
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> assertTime = end - start;

    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < iterations; ++i) {
        try {
            performOperationWithException(numbers, i % numbers.size());
        } catch (const std::out_of_range&) {
            // Do nothing
        }
    }
    end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> exceptionTime = end - start;

    std::cout << "Time with assertions: " << assertTime.count() << " seconds" << std::endl;
    std::cout << "Time with exceptions: " << exceptionTime.count() << " seconds" << std::endl;

    return 0;
}

Explanation

This example compares the performance of assertions versus exceptions:

Key Differences

  1. Purpose:
  2. Assertions: Used to catch logical errors and validate invariants during development and testing.
  3. Exceptions: Used to handle runtime errors and exceptional conditions in production code.

  4. Behavior:

  5. Assertions: Terminate the program immediately if the condition is false.
  6. Exceptions: Can be caught and handled, allowing for graceful error recovery.

  7. Performance:

  8. Assertions: Generally have no runtime cost in release builds (when NDEBUG is defined).
  9. Exceptions: Have some runtime overhead, especially when thrown and caught.

  10. Usage:

  11. Assertions: Best for checking internal consistency and programmer errors.
  12. Exceptions: Suitable for handling expected but uncommon situations and user errors.

  13. Compile-time vs. Runtime:

  14. Assertions: Can be completely disabled at compile-time.
  15. Exceptions: Always present in the code unless explicitly disabled through compiler flags.

Summary

Assertions and exceptions are complementary tools in C++ for handling different types of errors and unexpected conditions. Assertions are primarily used during development and testing to catch logical errors and validate invariants, offering a way to fail fast and loud when assumptions are violated. They are typically disabled in release builds for performance reasons.

Exceptions, on the other hand, are designed to handle runtime errors and exceptional conditions in production code. They provide a mechanism for error reporting and recovery, allowing the program to respond gracefully to unexpected situations.

When deciding between assertions and exceptions, consider the nature of the error (logical vs. runtime), the need for error recovery, performance implications, and whether the check should be present in release builds. Using both mechanisms appropriately can lead to more robust, maintainable, and efficient C++ code.

Related

Previous Page | Course Schedule | Course Content