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.
#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;
}
This example demonstrates the basic usage of assertions and exceptions:
divideWithAssert
uses an assertion to check for a zero divisor. If the assertion fails, the program terminates.divideWithException
throws an exception when the divisor is zero, which can be caught and handled.#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;
}
This example illustrates the use of assertions for invariants and exceptions for runtime errors:
at
method uses both an assertion and an exception check.#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;
}
This example compares the performance of assertions versus exceptions:
Exceptions: Used to handle runtime errors and exceptional conditions in production code.
Behavior:
Exceptions: Can be caught and handled, allowing for graceful error recovery.
Performance:
Exceptions: Have some runtime overhead, especially when thrown and caught.
Usage:
Exceptions: Suitable for handling expected but uncommon situations and user errors.
Compile-time vs. Runtime:
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.