C++ Exception Handling: Complete Guide with Examples
Master C++ exception handling: try-catch blocks, exception specifications, RAII pattern, and exception safety guarantees. Learn with practical examples and best practices.
try Block
Protected Code
catch Block
Exception Handler
throw
Raise Exception
noexcept
No Exceptions
Introduction to Exception Handling
Exception handling is a mechanism that allows programs to deal with unexpected situations (exceptions) during execution. It provides a structured way to separate error-handling code from normal program logic.
Why Use Exception Handling?
- Separates error handling from normal code
- Propagates errors up the call stack
- Provides clean-up via destructors (RAII)
- Makes code more robust and maintainable
- Handles errors that can't be handled locally
- Supports construction of error hierarchies
Exception Handling Components
- try: Marks block of code to monitor
- catch: Handles specific exceptions
- throw: Raises an exception
- noexcept: Specifies no exceptions
- RAII: Resource cleanup pattern
- Stack Unwinding: Automatic cleanup
Exception Handling Flow
1. Basic Exception Handling: try, catch, throw
The fundamental exception handling mechanism in C++ consists of three keywords: try, catch, and throw.
Basic Syntax
try {
// Code that might throw exceptions
if (error_condition) {
throw exception_object;
}
}
catch (exception_type& e) {
// Handle exception
}
#include <iostream>
#include <string>
#include <stdexcept>
#include <cmath>
using namespace std;
void demonstrateBasicExceptions() {
cout << "=== Basic Exception Handling ===" << endl;
// Example 1: Simple exception handling
try {
cout << "Enter a positive number: ";
int num;
cin >> num;
if (num <= 0) {
throw runtime_error("Number must be positive!");
}
cout << "You entered: " << num << endl;
}
catch (const runtime_error& e) {
cout << "Error: " << e.what() << endl;
}
// Example 2: Multiple catch blocks
try {
cout << "\nEnter two numbers (divisor can't be zero): ";
int a, b;
cin >> a >> b;
if (b == 0) {
throw "Division by zero!"; // Throwing string literal
}
double result = static_cast<double>(a) / b;
cout << "Result: " << result << endl;
}
catch (const char* error_msg) {
cout << "Caught string error: " << error_msg << endl;
}
catch (const exception& e) {
cout << "Caught standard exception: " << e.what() << endl;
}
catch (...) { // Catch-all handler
cout << "Caught unknown exception!" << endl;
}
// Example 3: Nested try-catch blocks
try {
cout << "\nOuter try block" << endl;
try {
cout << "Inner try block" << endl;
throw invalid_argument("Invalid argument from inner block");
}
catch (const invalid_argument& e) {
cout << "Inner catch: " << e.what() << endl;
// Can rethrow exception
throw; // Rethrow to outer handler
}
}
catch (const exception& e) {
cout << "Outer catch: " << e.what() << endl;
}
// Example 4: Exception in function
auto divide = [](int numerator, int denominator) -> double {
if (denominator == 0) {
throw domain_error("Cannot divide by zero");
}
return static_cast<double>(numerator) / denominator;
};
try {
cout << "\nDivision function test:" << endl;
cout << "10 / 2 = " << divide(10, 2) << endl;
cout << "5 / 0 = " << divide(5, 0) << endl; // Will throw
}
catch (const domain_error& e) {
cout << "Division error: " << e.what() << endl;
}
// Example 5: Custom exception class
class NegativeNumberException : public exception {
private:
string message;
public:
NegativeNumberException(const string& msg) : message(msg) {}
const char* what() const noexcept override {
return message.c_str();
}
};
auto squareRoot = [](double x) -> double {
if (x < 0) {
throw NegativeNumberException("Cannot calculate square root of negative number");
}
return sqrt(x);
};
try {
cout << "\nSquare root function test:" << endl;
cout << "sqrt(16) = " << squareRoot(16) << endl;
cout << "sqrt(-4) = " << squareRoot(-4) << endl; // Will throw
}
catch (const NegativeNumberException& e) {
cout << "Custom exception: " << e.what() << endl;
}
// Example 6: Exception propagation
cout << "\n=== Exception Propagation ===" << endl;
void functionC() {
cout << "In functionC, throwing exception..." << endl;
throw logic_error("Error from functionC");
}
void functionB() {
cout << "In functionB, calling functionC..." << endl;
functionC();
cout << "This won't be printed" << endl;
}
void functionA() {
cout << "In functionA, calling functionB..." << endl;
functionB();
cout << "This won't be printed either" << endl;
}
try {
functionA();
}
catch (const logic_error& e) {
cout << "Caught in main: " << e.what() << endl;
cout << "Exception propagated through functionA -> functionB -> functionC" << endl;
}
cout << endl;
}
void demonstrateExceptionObjects() {
cout << "=== Exception Objects and Information ===" << endl;
// Example 1: Standard exception hierarchy
try {
vector<int> v(5);
cout << "Accessing element at index 10..." << endl;
cout << v.at(10) << endl; // Throws out_of_range
}
catch (const out_of_range& e) {
cout << "Out of range error: " << e.what() << endl;
}
catch (const exception& e) {
cout << "Standard exception: " << e.what() << endl;
}
// Example 2: Exception with additional information
class FileNotFoundException : public exception {
private:
string filename;
string full_message;
public:
FileNotFoundException(const string& fname)
: filename(fname),
full_message("File not found: " + fname) {}
const char* what() const noexcept override {
return full_message.c_str();
}
const string& getFilename() const { return filename; }
};
try {
throw FileNotFoundException("data.txt");
}
catch (const FileNotFoundException& e) {
cout << "\nFile error: " << e.what() << endl;
cout << "Missing file: " << e.getFilename() << endl;
}
// Example 3: Polymorphic exception handling
try {
// Can throw different exception types
throw bad_alloc(); // Memory allocation failure
}
catch (const bad_alloc& e) {
cout << "\nMemory allocation failed: " << e.what() << endl;
}
catch (const bad_cast& e) {
cout << "\nBad cast: " << e.what() << endl;
}
catch (const exception& e) {
cout << "\nGeneric exception: " << e.what() << endl;
}
cout << endl;
}
void demonstrateCatchOrder() {
cout << "=== Catch Block Order ===" << endl;
// Important: Catch more specific exceptions first!
// WRONG ORDER:
try {
throw runtime_error("Test exception");
}
catch (const exception& e) { // Too general - catches everything
cout << "Caught in generic exception handler" << endl;
}
catch (const runtime_error& e) { // Never reached!
cout << "This will never execute" << endl;
}
// CORRECT ORDER:
try {
throw runtime_error("Test exception");
}
catch (const runtime_error& e) { // Specific handler first
cout << "Caught runtime_error: " << e.what() << endl;
}
catch (const exception& e) { // Generic handler last
cout << "Caught generic exception: " << e.what() << endl;
}
// Catch-all handler (...) should always be last
try {
throw "String exception";
}
catch (const runtime_error& e) {
cout << "Runtime error" << endl;
}
catch (const exception& e) {
cout << "Standard exception" << endl;
}
catch (...) { // Catch-all must be last
cout << "Caught unknown exception type" << endl;
}
cout << endl;
}
int main() {
demonstrateBasicExceptions();
demonstrateExceptionObjects();
demonstrateCatchOrder();
return 0;
}
- Throw by value, catch by const reference
- Order catch blocks from specific to general
- Always handle exceptions at appropriate level
- Use RAII for resource management
- Avoid throwing exceptions from destructors
- Document exception guarantees
- Catching exceptions by value (slicing)
- Empty catch blocks (swallowing exceptions)
- Throwing exceptions from destructors
- Incorrect catch block order
- Memory leaks in exception paths
- Exception safety violations
2. Standard Exception Classes
C++ provides a hierarchy of standard exception classes in the <stdexcept> header. These classes represent common error conditions.
Standard Exception Hierarchy
Logic Errors (Preventable)
logic_errorinvalid_argumentdomain_errorlength_errorout_of_rangefuture_error(C++11)
Runtime Errors (Unavoidable)
runtime_errorrange_erroroverflow_errorunderflow_errorsystem_error(C++11)
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include <limits>
#include <fstream>
#include <memory>
using namespace std;
void demonstrateLogicErrors() {
cout << "=== Logic Error Exceptions ===" << endl;
// 1. invalid_argument - invalid parameter value
try {
auto calculateFactorial = [](int n) -> long long {
if (n < 0) {
throw invalid_argument("Factorial not defined for negative numbers");
}
if (n > 20) {
throw invalid_argument("Factorial too large for long long");
}
long long result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
};
cout << "5! = " << calculateFactorial(5) << endl;
cout << "-3! = " << calculateFactorial(-3) << endl; // Throws
}
catch (const invalid_argument& e) {
cout << "Invalid argument: " << e.what() << endl;
}
// 2. domain_error - domain violation
try {
auto safeDivide = [](double a, double b) -> double {
if (b == 0.0) {
throw domain_error("Division by zero");
}
return a / b;
};
cout << "\n10 / 2 = " << safeDivide(10, 2) << endl;
cout << "5 / 0 = " << safeDivide(5, 0) << endl; // Throws
}
catch (const domain_error& e) {
cout << "Domain error: " << e.what() << endl;
}
// 3. length_error - exceeds maximum size
try {
vector<int> v;
v.reserve(v.max_size() - 1);
cout << "\nAttempting to reserve too much memory..." << endl;
v.reserve(v.max_size() + 1); // Throws length_error
}
catch (const length_error& e) {
cout << "Length error: " << e.what() << endl;
}
// 4. out_of_range - index out of bounds
try {
vector<int> numbers = {1, 2, 3, 4, 5};
cout << "\nAccessing vector elements:" << endl;
cout << "numbers[2] = " << numbers[2] << endl;
cout << "numbers.at(10) = " << numbers.at(10) << endl; // Throws
}
catch (const out_of_range& e) {
cout << "Out of range: " << e.what() << endl;
}
// 5. logic_error - general logic error
try {
auto validateAge = [](int age) {
if (age < 0 || age > 150) {
throw logic_error("Invalid age value");
}
return age;
};
cout << "\nAge 25: " << validateAge(25) << endl;
cout << "Age -5: " << validateAge(-5) << endl; // Throws
}
catch (const logic_error& e) {
cout << "Logic error: " << e.what() << endl;
}
cout << endl;
}
void demonstrateRuntimeErrors() {
cout << "=== Runtime Error Exceptions ===" << endl;
// 1. range_error - result outside valid range
try {
auto calculatePercentage = [](double part, double whole) -> double {
if (whole == 0.0) {
throw runtime_error("Whole cannot be zero");
}
double percentage = (part / whole) * 100.0;
if (percentage < 0.0 || percentage > 100.0) {
throw range_error("Percentage out of valid range [0, 100]");
}
return percentage;
};
cout << "50 of 200 = " << calculatePercentage(50, 200) << "%" << endl;
cout << "150 of 100 = " << calculatePercentage(150, 100) << "%" << endl; // Throws
}
catch (const range_error& e) {
cout << "Range error: " << e.what() << endl;
}
catch (const runtime_error& e) {
cout << "Runtime error: " << e.what() << endl;
}
// 2. overflow_error - arithmetic overflow
try {
auto safeAdd = [](int a, int b) -> int {
if ((b > 0) && (a > numeric_limits<int>::max() - b)) {
throw overflow_error("Integer overflow in addition");
}
if ((b < 0) && (a < numeric_limits<int>::min() - b)) {
throw overflow_error("Integer underflow in addition");
}
return a + b;
};
cout << "\n100 + 200 = " << safeAdd(100, 200) << endl;
cout << "MAX_INT + 1 = " << safeAdd(numeric_limits<int>::max(), 1) << endl; // Throws
}
catch (const overflow_error& e) {
cout << "Overflow error: " << e.what() << endl;
}
// 3. underflow_error - arithmetic underflow
try {
auto safeDivideDouble = [](double a, double b) -> double {
double result = a / b;
// Check for underflow (result too close to zero)
if (result != 0.0 && abs(result) < numeric_limits<double>::min()) {
throw underflow_error("Floating point underflow");
}
return result;
};
cout << "\n1.0 / 2.0 = " << safeDivideDouble(1.0, 2.0) << endl;
// Note: Actual underflow is hard to demonstrate without special values
}
catch (const underflow_error& e) {
cout << "Underflow error: " << e.what() << endl;
}
// 4. system_error (C++11) - system/library errors
try {
ifstream file("nonexistent_file.txt");
file.exceptions(ifstream::failbit); // Throw on failure
string line;
getline(file, line); // Throws system_error if file doesn't exist
}
catch (const system_error& e) {
cout << "\nSystem error: " << e.what() << endl;
cout << "Error code: " << e.code() << endl;
cout << "Error category: " << e.code().category().name() << endl;
}
cout << endl;
}
void demonstrateBadAllocations() {
cout << "=== Bad Allocation Exceptions ===" << endl;
// bad_alloc - memory allocation failure
try {
cout << "Attempting to allocate massive memory..." << endl;
// Try to allocate more memory than available
size_t huge_size = numeric_limits<size_t>::max() / 2;
int* massive_array = new int[huge_size];
delete[] massive_array;
}
catch (const bad_alloc& e) {
cout << "Bad allocation: " << e.what() << endl;
cout << "Memory allocation failed!" << endl;
}
// bad_array_new_length (C++11) - invalid array length
try {
cout << "\nAttempting to create array with negative size..." << endl;
size_t negative_size = -1; // Large positive due to wrap-around
int* bad_array = new int[negative_size];
delete[] bad_array;
}
catch (const bad_array_new_length& e) {
cout << "Bad array new length: " << e.what() << endl;
}
cout << endl;
}
void demonstrateTypeConversions() {
cout << "=== Type Conversion Exceptions ===" << endl;
// bad_cast - failed dynamic_cast on references
try {
class Base {
public:
virtual ~Base() = default;
};
class Derived : public Base {
public:
void specificMethod() {
cout << "Derived specific method" << endl;
}
};
Base base_obj;
// This will throw bad_cast because base_obj is not a Derived
Derived& derived_ref = dynamic_cast<Derived&>(base_obj);
derived_ref.specificMethod();
}
catch (const bad_cast& e) {
cout << "Bad cast: " << e.what() << endl;
cout << "dynamic_cast failed on reference" << endl;
}
// bad_typeid - typeid on null pointer
try {
class PolyBase {
public:
virtual ~PolyBase() = default;
};
PolyBase* ptr = nullptr;
// typeid on null pointer of polymorphic type throws bad_typeid
cout << "Type: " << typeid(*ptr).name() << endl;
}
catch (const bad_typeid& e) {
cout << "\nBad typeid: " << e.what() << endl;
cout << "typeid called on null pointer of polymorphic type" << endl;
}
cout << endl;
}
void demonstrateExceptionHierarchy() {
cout << "=== Exception Class Hierarchy ===" << endl;
// Demonstrating polymorphism in exception handling
try {
// Can throw any exception type
throw out_of_range("Index out of bounds");
}
catch (const out_of_range& e) {
cout << "Caught out_of_range (most specific): " << e.what() << endl;
}
catch (const logic_error& e) {
cout << "Caught logic_error: " << e.what() << endl;
}
catch (const exception& e) {
cout << "Caught exception (most general): " << e.what() << endl;
}
cout << "\nException class relationships:" << endl;
cout << "exception (base class)" << endl;
cout << "├── logic_error" << endl;
cout << "│ ├── invalid_argument" << endl;
cout << "│ ├── domain_error" << endl;
cout << "│ ├── length_error" << endl;
cout << "│ └── out_of_range" << endl;
cout << "└── runtime_error" << endl;
cout << " ├── range_error" << endl;
cout << " ├── overflow_error" << endl;
cout << " ├── underflow_error" << endl;
cout << " └── system_error (C++11)" << endl;
cout << endl;
cout << "Other exception types (not derived from std::exception):" << endl;
cout << "- bad_alloc (memory allocation)" << endl;
cout << "- bad_cast (dynamic_cast failure)" << endl;
cout << "- bad_typeid (typeid on null pointer)" << endl;
cout << "- bad_exception (unexpected exception)" << endl;
cout << "- bad_array_new_length (C++11)" << endl;
cout << "- bad_weak_ptr (C++11)" << endl;
cout << endl;
}
int main() {
demonstrateLogicErrors();
demonstrateRuntimeErrors();
demonstrateBadAllocations();
demonstrateTypeConversions();
demonstrateExceptionHierarchy();
return 0;
}
Choosing the Right Exception Type
Use logic_error for preventable programming errors. Use runtime_error for errors that could occur despite correct program logic. Always prefer standard exceptions over custom ones when they fit.
3. Exception Specifications and noexcept
Exception specifications declare what exceptions a function might throw. noexcept (C++11) indicates a function won't throw exceptions.
Exception Specification Syntax
Dynamic Exception Specifications (Deprecated)
// C++98 style (deprecated)
void func() throw(int, std::runtime_error);
void func() throw(); // No exceptions
noexcept (C++11)
// Modern C++ style
void func() noexcept; // Won't throw
void func() noexcept(true);
void func() noexcept(false);
#include <iostream>
#include <stdexcept>
#include <vector>
#include <type_traits>
#include <string>
using namespace std;
void demonstrateNoexcept() {
cout << "=== noexcept Specifier (C++11) ===" << endl;
// 1. Basic noexcept usage
void safeFunction() noexcept {
cout << "This function promises not to throw exceptions" << endl;
// All operations here must not throw
}
void potentiallyThrowingFunction() {
throw runtime_error("This function might throw");
}
// 2. noexcept operator - checks if expression is noexcept
cout << "safeFunction is noexcept? "
<< (noexcept(safeFunction()) ? "Yes" : "No") << endl;
cout << "potentiallyThrowingFunction is noexcept? "
<< (noexcept(potentiallyThrowingFunction()) ? "Yes" : "No") << endl;
// 3. Conditional noexcept
template<typename T>
void swapValues(T& a, T& b) noexcept(noexcept(T(std::move(a))) &&
noexcept(a.~T())) {
// This is noexcept if T's move constructor and destructor are noexcept
T temp(std::move(a));
a = std::move(b);
b = std::move(temp);
}
// 4. noexcept in different contexts
class NoThrowClass {
public:
// Constructor with noexcept
NoThrowClass() noexcept {
cout << "NoThrowClass constructor (noexcept)" << endl;
}
// Destructor should almost always be noexcept
~NoThrowClass() noexcept {
cout << "NoThrowClass destructor (noexcept)" << endl;
}
// Copy constructor with noexcept
NoThrowClass(const NoThrowClass&) noexcept {
cout << "NoThrowClass copy constructor (noexcept)" << endl;
}
// Move constructor with noexcept
NoThrowClass(NoThrowClass&&) noexcept {
cout << "NoThrowClass move constructor (noexcept)" << endl;
}
};
class MightThrowClass {
public:
MightThrowClass() {
// Might throw
}
~MightThrowClass() {
// Destructors should not throw!
}
};
// 5. noexcept and STL
vector<NoThrowClass> vec1;
vec1.push_back(NoThrowClass()); // Efficient with noexcept move
cout << "\nNoexcept checks on standard types:" << endl;
cout << "int move is noexcept? " << is_nothrow_move_constructible<int>::value << endl;
cout << "string move is noexcept? " << is_nothrow_move_constructible<string>::value << endl;
// 6. noexcept and optimization
auto optimizedFunction = [](int x) noexcept -> int {
// Compiler can optimize better knowing no exceptions
return x * 2;
};
cout << "\nOptimized function result: " << optimizedFunction(5) << endl;
// 7. Violating noexcept (calls std::terminate)
cout << "\nTesting noexcept violation..." << endl;
void violatingFunction() noexcept {
throw runtime_error("This violates noexcept!"); // Will call terminate()
}
try {
// Uncomment to see terminate() being called
// violatingFunction();
}
catch (...) {
cout << "Exception caught (but won't happen for noexcept function)" << endl;
}
cout << endl;
}
void demonstrateDeprecatedExceptionSpecifications() {
cout << "=== Deprecated Exception Specifications (C++98) ===" << endl;
// Note: These are deprecated in C++11 and removed in C++17
// They're shown here for educational purposes only
// Dynamic exception specification (deprecated)
void oldStyleFunction() throw(runtime_error, logic_error) {
// Can only throw runtime_error or logic_error
throw runtime_error("Old style exception");
}
void noThrowOldStyle() throw() {
// Should not throw any exceptions
// But check happens at runtime, not compile time
}
try {
oldStyleFunction();
}
catch (const runtime_error& e) {
cout << "Caught runtime_error from old-style function: " << e.what() << endl;
}
cout << "\nWhy dynamic exception specifications are deprecated:" << endl;
cout << "1. Runtime overhead for checking" << endl;
cout << "2. Poor interaction with templates" << endl;
cout << "3. Not enforced at compile time" << endl;
cout << "4. Calls unexpected() then terminate() on violation" << endl;
cout << "5. noexcept is better in every way" << endl;
cout << endl;
}
void demonstrateExceptionSpecificationsInPractice() {
cout << "=== Exception Specifications in Practice ===" << endl;
// 1. When to use noexcept
cout << "Use noexcept for:" << endl;
cout << "- Move constructors and move assignment operators" << endl;
cout << "- Destructors (always!)" << endl;
cout << "- Swap functions" << endl;
cout << "- Simple getters and trivial functions" << endl;
cout << "- Functions that only call other noexcept functions" << endl;
cout << "\n2. Example: Resource management class" << endl;
class ResourceManager {
private:
vector<int> resources;
public:
ResourceManager() noexcept = default;
// Destructor must be noexcept
~ResourceManager() noexcept {
// Clean up resources
}
// Move constructor should be noexcept
ResourceManager(ResourceManager&& other) noexcept
: resources(std::move(other.resources)) {}
// Move assignment should be noexcept
ResourceManager& operator=(ResourceManager&& other) noexcept {
resources = std::move(other.resources);
return *this;
}
// This function might throw
void addResource(int value) {
if (value < 0) {
throw invalid_argument("Resource value cannot be negative");
}
resources.push_back(value);
}
// This function is noexcept
size_t getResourceCount() const noexcept {
return resources.size();
}
};
// 3. Template programming with noexcept
template<typename T>
class SafeContainer {
private:
T* data;
size_t size;
public:
// Constructor is noexcept if T's constructor is noexcept
SafeContainer(size_t n) noexcept(noexcept(T()))
: data(new T[n]), size(n) {}
// Destructor is noexcept (must be!)
~SafeContainer() noexcept {
delete[] data;
}
// Move constructor
SafeContainer(SafeContainer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
};
// 4. noexcept in function pointers
using NoExceptFunc = void(*)() noexcept;
using MightThrowFunc = void(*)();
void noThrowFunc() noexcept {
cout << "No-throw function" << endl;
}
void mightThrowFunc() {
cout << "Might-throw function" << endl;
}
NoExceptFunc ptr1 = noThrowFunc; // OK
// NoExceptFunc ptr2 = mightThrowFunc; // Error: not noexcept
cout << "\n3. Performance implications:" << endl;
cout << "noexcept allows compilers to:" << endl;
cout << "- Generate more efficient code" << endl;
cout << "- Omit exception handling overhead" << endl;
cout << "- Enable move semantics in more cases" << endl;
cout << "- Optimize across function boundaries" << endl;
// 5. Testing noexcept at compile time
cout << "\n4. Compile-time noexcept checking:" << endl;
struct NoThrowType {
NoThrowType() noexcept = default;
~NoThrowType() noexcept = default;
};
struct MightThrowType {
MightThrowType() {} // Might throw
};
cout << "NoThrowType default constructor is noexcept? "
<< is_nothrow_default_constructible<NoThrowType>::value << endl;
cout << "MightThrowType default constructor is noexcept? "
<< is_nothrow_default_constructible<MightThrowType>::value << endl;
cout << "\n5. Best practices summary:" << endl;
cout << "- Always mark destructors as noexcept" << endl;
cout <<- Mark move operations as noexcept when possible" << endl;
cout << "- Use noexcept for simple functions that can't throw" << endl;
cout << "- Don't use noexcept(false) unless necessary" << endl;
cout << "- Consider noexcept as part of your interface contract" << endl;
cout << endl;
}
void demonstrateNoexceptInAlgorithms() {
cout << "=== noexcept in STL Algorithms ===" << endl;
// STL algorithms can be more efficient with noexcept types
vector<string> strings = {"apple", "banana", "cherry"};
cout << "Original vector: ";
for (const auto& s : strings) cout << s << " ";
cout << endl;
// std::move with noexcept types is more efficient
vector<string> moved_strings;
moved_strings.reserve(strings.size());
// If string's move constructor is noexcept, this is efficient
for (auto& s : strings) {
moved_strings.push_back(std::move(s));
}
cout << "After move: strings size = " << strings.size()
<< ", moved_strings size = " << moved_strings.size() << endl;
// Check noexcept properties of standard types
cout << "\nStandard type noexcept properties:" << endl;
cout << "int: " << endl;
cout << " Default construct: " << is_nothrow_default_constructible<int>::value << endl;
cout << " Copy construct: " << is_nothrow_copy_constructible<int>::value << endl;
cout << " Move construct: " << is_nothrow_move_constructible<int>::value << endl;
cout << "\nstring (C++11 and later):" << endl;
cout << " Move construct: " << is_nothrow_move_constructible<string>::value << endl;
cout << " Move assign: " << is_nothrow_move_assignable<string>::value << endl;
// noexcept and swap
cout << "\nstd::swap is noexcept if types are nothrow move constructible/assignable" << endl;
cout << "swap(int, int) is noexcept? " << noexcept(swap(declval<int>(), declval<int>())) << endl;
cout << endl;
}
int main() {
demonstrateNoexcept();
demonstrateDeprecatedExceptionSpecifications();
demonstrateExceptionSpecificationsInPractice();
demonstrateNoexceptInAlgorithms();
return 0;
}
noexcept (C++11+)
- Compile-time specification
- Better performance optimization
- Calls
std::terminate()on violation - Can be conditional
- Recommended for modern C++
throw() (Deprecated)
- Runtime checking (deprecated)
- Performance overhead
- Calls
unexpected()thenterminate() - Removed in C++17
- Avoid in new code
No Specification
- Function may throw any exception
- Most flexible but least informative
- No performance penalty for checking
- Use when exception behavior varies
- Document exception behavior in comments
4. RAII and Exception Safety
RAII (Resource Acquisition Is Initialization) is a fundamental C++ idiom that ties resource management to object lifetime, ensuring proper cleanup even when exceptions occur.
RAII Principle
class Resource {
Handle* handle;
public:
Resource() : handle(acquire_resource()) {
// Resource acquired in constructor
}
~Resource() {
release_resource(handle); // Resource released in destructor
}
// Disable copying (or implement properly)
Resource(const Resource&) = delete;
Resource& operator=(const Resource&) = delete;
};
#include <iostream>
#include <memory>
#include <fstream>
#include <vector>
#include <stdexcept>
#include <mutex>
using namespace std;
void demonstrateBasicRAII() {
cout << "=== Basic RAII Examples ===" << endl;
// 1. Manual resource management (BAD)
cout << "1. Manual resource management (error-prone):" << endl;
void manualResourceManagement() {
int* array = new int[100];
try {
// Some operations that might throw
throw runtime_error("Something went wrong");
delete[] array; // This might not be reached!
}
catch (...) {
delete[] array; // Must remember to cleanup in catch!
throw;
}
}
// 2. RAII with smart pointers (GOOD)
cout << "\n2. RAII with unique_ptr (automatic cleanup):" << endl;
void raiiWithSmartPointer() {
unique_ptr<int[]> array(new int[100]);
// Operations that might throw
throw runtime_error("Something went wrong");
// No need to manually delete - unique_ptr destructor does it
}
try {
raiiWithSmartPointer();
}
catch (const exception& e) {
cout << "Exception caught, but resources were automatically cleaned up!" << endl;
}
// 3. Custom RAII class for file handling
cout << "\n3. Custom RAII File Wrapper:" << endl;
class FileRAII {
private:
FILE* file;
public:
FileRAII(const char* filename, const char* mode)
: file(fopen(filename, mode)) {
if (!file) {
throw runtime_error("Failed to open file");
}
cout << "File opened: " << filename << endl;
}
~FileRAII() {
if (file) {
fclose(file);
cout << "File closed automatically" << endl;
}
}
// Delete copy operations
FileRAII(const FileRAII&) = delete;
FileRAII& operator=(const FileRAII&) = delete;
// Allow move operations
FileRAII(FileRAII&& other) noexcept : file(other.file) {
other.file = nullptr;
}
FileRAII& operator=(FileRAII&& other) noexcept {
if (this != &other) {
if (file) fclose(file);
file = other.file;
other.file = nullptr;
}
return *this;
}
FILE* get() const { return file; }
};
try {
FileRAII file("test.txt", "w");
// Use file.get() for operations
// File automatically closed when 'file' goes out of scope
}
catch (const exception& e) {
cout << "File error: " << e.what() << endl;
}
cout << endl;
}
void demonstrateExceptionSafetyGuarantees() {
cout << "=== Exception Safety Guarantees ===" << endl;
// Three levels of exception safety:
cout << "1. No-throw guarantee (strongest)" << endl;
cout << " - Operation never throws exceptions" << endl;
cout << " - Marked with noexcept" << endl;
cout << "\n2. Strong guarantee (commit-or-rollback)" << endl;
cout << " - Operation either completes successfully" << endl;
cout << " - Or has no effect if exception occurs" << endl;
cout << " - State unchanged on failure" << endl;
cout << "\n3. Basic guarantee" << endl;
cout << " - No resource leaks on exception" << endl;
cout << " - Objects remain in valid (but unspecified) state" << endl;
cout << " - Program continues execution" << endl;
cout << "\n4. No guarantee (weakest)" << endl;
cout << " - Resource leaks possible" << endl;
cout << " - Objects may be left in invalid state" << endl;
cout << " - Program may crash" << endl;
// Examples of different guarantees
cout << "\n=== Examples ===" << endl;
// 1. No-throw guarantee example
class NoThrowStack {
private:
vector<int> data;
public:
void push(int value) noexcept {
try {
data.push_back(value);
}
catch (...) {
// Handle exception internally
// Still maintains no-throw guarantee
}
}
bool empty() const noexcept {
return data.empty();
}
};
// 2. Strong guarantee example
class Account {
private:
double balance;
public:
Account(double initial) : balance(initial) {}
// Strong guarantee: either both transfers succeed or neither does
void transferFunds(Account& to, double amount) {
if (amount <= 0) {
throw invalid_argument("Amount must be positive");
}
if (amount > balance) {
throw runtime_error("Insufficient funds");
}
// Create temporary copies
double old_from = balance;
double old_to = to.balance;
try {
balance -= amount;
to.balance += amount;
}
catch (...) {
// Rollback on exception
balance = old_from;
to.balance = old_to;
throw;
}
}
};
// 3. Basic guarantee example
class BasicGuaranteeStack {
private:
int* data;
size_t capacity;
size_t size;
public:
BasicGuaranteeStack() : data(nullptr), capacity(0), size(0) {}
~BasicGuaranteeStack() {
delete[] data;
}
void push(int value) {
if (size == capacity) {
// Reallocation might throw, but we handle it
size_t new_capacity = capacity == 0 ? 1 : capacity * 2;
int* new_data = new(nothrow) int[new_capacity];
if (!new_data) {
throw bad_alloc();
}
// Copy existing elements (might throw)
for (size_t i = 0; i < size; ++i) {
new_data[i] = data[i];
}
delete[] data;
data = new_data;
capacity = new_capacity;
}
data[size++] = value; // Might throw for complex types
}
// Basic guarantee: if push fails, stack remains valid
// (might have reallocated but original data preserved or restored)
};
cout << "\nDesigning for exception safety:" << endl;
cout << "1. Use RAII for all resources" << endl;
cout << "2. Perform operations that might fail on copies/temporaries" << endl;
cout << "3. Commit changes only after all operations succeed" << endl;
cout << "4. Provide at least basic guarantee for all operations" << endl;
cout << endl;
}
void demonstrateCopyAndSwap() {
cout << "=== Copy-and-Swap Idiom for Strong Exception Safety ===" << endl;
// Copy-and-swap provides strong exception safety
class String {
private:
char* data;
size_t length;
void free() {
delete[] data;
data = nullptr;
length = 0;
}
void copyFrom(const String& other) {
length = other.length;
data = new char[length + 1];
for (size_t i = 0; i <= length; ++i) {
data[i] = other.data[i];
}
}
public:
// Constructor
String(const char* str = "") {
length = 0;
while (str[length] != '\0') ++length;
data = new char[length + 1];
for (size_t i = 0; i <= length; ++i) {
data[i] = str[i];
}
}
// Destructor
~String() {
free();
}
// Copy constructor
String(const String& other) {
copyFrom(other);
}
// Copy assignment with copy-and-swap (strong exception safety)
String& operator=(String other) { // Pass by value (copy)
swap(*this, other); // Non-throwing swap
return *this;
}
// Move constructor
String(String&& other) noexcept
: data(other.data), length(other.length) {
other.data = nullptr;
other.length = 0;
}
// Move assignment
String& operator=(String&& other) noexcept {
if (this != &other) {
free();
data = other.data;
length = other.length;
other.data = nullptr;
other.length = 0;
}
return *this;
}
// Swap function (noexcept)
friend void swap(String& a, String& b) noexcept {
using std::swap;
swap(a.data, b.data);
swap(a.length, b.length);
}
const char* c_str() const { return data; }
};
// Test copy-and-swap
String s1 = "Hello";
String s2 = "World";
cout << "Before swap:" << endl;
cout << "s1: " << s1.c_str() << endl;
cout << "s2: " << s2.c_str() << endl;
swap(s1, s2);
cout << "\nAfter swap:" << endl;
cout << "s1: " << s1.c_str() << endl;
cout << "s2: " << s2.c_str() << endl;
// Copy assignment provides strong guarantee
s1 = s2; // If allocation fails in copy, s1 remains unchanged
cout << "\nBenefits of copy-and-swap:" << endl;
cout << "1. Provides strong exception safety" << endl;
cout << "2. Eliminates code duplication" << endl;
cout << "3. Handles self-assignment correctly" << endl;
cout << "4. Works with move semantics" << endl;
cout << endl;
}
void demonstrateResourceManagementPatterns() {
cout << "=== Resource Management Patterns ===" << endl;
// 1. Scoped Lock pattern
class ScopedLock {
private:
mutex& mtx;
public:
explicit ScopedLock(mutex& m) : mtx(m) {
mtx.lock();
cout << "Mutex locked" << endl;
}
~ScopedLock() {
mtx.unlock();
cout << "Mutex unlocked" << endl;
}
// Delete copy operations
ScopedLock(const ScopedLock&) = delete;
ScopedLock& operator=(const ScopedLock&) = delete;
};
mutex mtx;
{
ScopedLock lock(mtx); // Automatically locks
// Critical section
// Mutex automatically unlocked when lock goes out of scope
}
// 2. Transaction pattern
class DatabaseTransaction {
private:
bool committed;
void beginTransaction() {
cout << "Transaction began" << endl;
}
void rollbackTransaction() {
cout << "Transaction rolled back" << endl;
}
void commitTransaction() {
cout << "Transaction committed" << endl;
}
public:
DatabaseTransaction() : committed(false) {
beginTransaction();
}
~DatabaseTransaction() {
if (!committed) {
rollbackTransaction();
}
}
void commit() {
commitTransaction();
committed = true;
}
// Execute operation with rollback on failure
template<typename Func>
void execute(Func operation) {
try {
operation();
}
catch (...) {
rollbackTransaction();
throw;
}
}
};
// 3. Scope Guard (C++11 and later)
class ScopeGuard {
private:
function<void()> cleanup;
public:
explicit ScopeGuard(function<void()> f) : cleanup(f) {}
~ScopeGuard() {
if (cleanup) {
cleanup();
}
}
// Release the cleanup (for commit scenarios)
void release() {
cleanup = nullptr;
}
// Delete copy operations
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
};
// Using scope guard
cout << "\nScope Guard example:" << endl;
{
int* resource = new int(42);
ScopeGuard guard([&resource]() {
delete resource;
cout << "Resource cleaned up by scope guard" << endl;
});
// Use resource
// If exception occurs here, guard cleans up
guard.release(); // Explicit cleanup if successful
delete resource;
cout << "Resource cleaned up explicitly" << endl;
}
cout << "\nRAII Best Practices:" << endl;
cout << "1. Acquire resources in constructors" << endl;
cout << "2. Release resources in destructors" << endl;
cout << "3. Use smart pointers for dynamic memory" << endl;
cout << "4. Make swap operations noexcept" << endl;
cout << "5. Provide at least basic exception safety" << endl;
cout << "6. Document exception safety guarantees" << endl;
cout << endl;
}
void demonstrateSmartPointersAndRAII() {
cout << "=== Smart Pointers as RAII Wrappers ===" << endl;
// 1. unique_ptr - exclusive ownership
cout << "1. unique_ptr (exclusive ownership):" << endl;
{
unique_ptr<int> ptr(new int(42));
cout << "Value: " << *ptr << endl;
// Automatically deleted when out of scope
}
// 2. shared_ptr - shared ownership
cout << "\n2. shared_ptr (shared ownership):" << endl;
{
shared_ptr<int> ptr1 = make_shared<int>(100);
shared_ptr<int> ptr2 = ptr1; // Both share ownership
cout << "Use count: " << ptr1.use_count() << endl;
cout << "Value via ptr1: " << *ptr1 << endl;
cout << "Value via ptr2: " << *ptr2 << endl;
}
// 3. Custom deleter with unique_ptr
cout << "\n3. Custom deleter:" << endl;
{
FILE* file = fopen("test.txt", "w");
if (file) {
unique_ptr<FILE, decltype(&fclose)> filePtr(file, fclose);
// File automatically closed when filePtr goes out of scope
cout << "File will be automatically closed" << endl;
}
}
// 4. RAII with containers
cout << "\n4. RAII with containers:" << endl;
{
vector<unique_ptr<int>> numbers;
numbers.push_back(make_unique<int>(1));
numbers.push_back(make_unique<int>(2));
numbers.push_back(make_unique<int>(3));
// All memory automatically managed
for (const auto& ptr : numbers) {
cout << *ptr << " ";
}
cout << endl;
}
cout << "\nBenefits of smart pointers:" << endl;
cout << "- Automatic memory management" << endl;
cout << "- Exception safety" << endl;
cout << "- Clear ownership semantics" << endl;
cout << "- No manual new/delete" << endl;
cout << "- Custom deleters for non-memory resources" << endl;
cout << endl;
}
int main() {
demonstrateBasicRAII();
demonstrateExceptionSafetyGuarantees();
demonstrateCopyAndSwap();
demonstrateResourceManagementPatterns();
demonstrateSmartPointersAndRAII();
return 0;
}
- Never throw from destructors - can cause
std::terminate() - Use RAII for all resource management
- Provide at least basic exception safety guarantee
- Aim for strong guarantee for key operations
- Use copy-and-swap for assignment operators
- Make swap operations
noexcept - Document exception safety guarantees in comments
5. Advanced Exception Handling Techniques
Advanced exception handling patterns including nested exceptions, exception propagation, and handling strategies for large systems.
#include <iostream>
#include <stdexcept>
#include <memory>
#include <vector>
#include <fstream>
#include <system_error>
#include <future>
#include <thread>
using namespace std;
void demonstrateNestedExceptions() {
cout << "=== Nested Exceptions (C++11) ===" << endl;
// Nested exceptions allow preserving exception context
// 1. Basic nested exception usage
void lowLevelFunction() {
throw runtime_error("Low-level error occurred");
}
void midLevelFunction() {
try {
lowLevelFunction();
}
catch (...) {
// Wrap current exception in nested_exception
throw_with_nested(
runtime_error("Mid-level function failed")
);
}
}
void highLevelFunction() {
try {
midLevelFunction();
}
catch (...) {
throw_with_nested(
logic_error("High-level operation failed")
);
}
}
// 2. Catching and unwrapping nested exceptions
try {
highLevelFunction();
}
catch (const exception& e) {
cout << "Caught exception: " << e.what() << endl;
// Try to rethrow nested exception
try {
rethrow_if_nested(e);
}
catch (const exception& nested) {
cout << " Nested: " << nested.what() << endl;
// Could continue unwrapping...
try {
rethrow_if_nested(nested);
}
catch (const exception& inner) {
cout << " Inner: " << inner.what() << endl;
}
}
}
// 3. Custom nested exception class
class DatabaseException : public nested_exception {
private:
string operation;
public:
DatabaseException(const string& op) : operation(op) {}
const char* what() const noexcept override {
return ("Database operation failed: " + operation).c_str();
}
};
void databaseOperation() {
try {
throw runtime_error("Connection timeout");
}
catch (...) {
throw_with_nested(DatabaseException("SELECT * FROM users"));
}
}
try {
databaseOperation();
}
catch (const DatabaseException& e) {
cout << "\nDatabase error: " << e.what() << endl;
// Print nested exception chain
auto print_exception_chain = [](const exception& e, int level = 0) {
cout << string(level * 2, ' ') << e.what() << endl;
try {
rethrow_if_nested(e);
}
catch (const exception& nested) {
print_exception_chain(nested, level + 1);
}
};
print_exception_chain(e);
}
cout << "\nBenefits of nested exceptions:" << endl;
cout << "1. Preserve full error context" << endl;
cout << "2. Debugging with complete stack trace" << endl;
cout << "3. Error reporting with full details" << endl;
cout << "4. Useful in layered architectures" << endl;
cout << endl;
}
void demonstrateExceptionPtr() {
cout << "=== std::exception_ptr (C++11) ===" << endl;
// exception_ptr can store exceptions for later use
// 1. Basic exception_ptr usage
exception_ptr saved_exception;
void mightThrow() {
throw runtime_error("An error occurred");
}
void saveException() {
try {
mightThrow();
}
catch (...) {
saved_exception = current_exception();
}
}
saveException();
// Re-throw saved exception later
if (saved_exception) {
cout << "Re-throwing saved exception:" << endl;
try {
rethrow_exception(saved_exception);
}
catch (const exception& e) {
cout << "Caught: " << e.what() << endl;
}
}
// 2. Exception_ptr with asynchronous operations
vector<exception_ptr> exceptions;
auto task1 = []() -> int {
throw runtime_error("Task 1 failed");
return 1;
};
auto task2 = []() -> int {
return 42; // Success
};
auto task3 = []() -> int {
throw logic_error("Task 3 logic error");
return 3;
};
// Simulate async execution
vector<function<int()>> tasks = {task1, task2, task3};
for (auto& task : tasks) {
try {
int result = task();
cout << "Task succeeded: " << result << endl;
}
catch (...) {
exceptions.push_back(current_exception());
}
}
// Handle all exceptions later
cout << "\nProcessing " << exceptions.size() << " saved exceptions:" << endl;
for (const auto& ex_ptr : exceptions) {
try {
rethrow_exception(ex_ptr);
}
catch (const runtime_error& e) {
cout << "Runtime error: " << e.what() << endl;
}
catch (const logic_error& e) {
cout << "Logic error: " << e.what() << endl;
}
catch (const exception& e) {
cout << "Generic error: " << e.what() << endl;
}
}
// 3. Exception_ptr in multithreading
cout << "\nException handling in threads:" << endl;
exception_ptr thread_exception;
auto thread_func = [&thread_exception]() {
try {
// Simulate work that might fail
this_thread::sleep_for(chrono::milliseconds(100));
throw runtime_error("Thread execution failed");
}
catch (...) {
thread_exception = current_exception();
}
};
thread t(thread_func);
t.join();
if (thread_exception) {
try {
rethrow_exception(thread_exception);
}
catch (const exception& e) {
cout << "Thread threw: " << e.what() << endl;
}
}
cout << "\nUse cases for exception_ptr:" << endl;
cout << "1. Asynchronous exception handling" << endl;
cout << "2. Exception propagation across threads" << endl;
cout << "3. Deferred exception handling" << endl;
cout << "4. Error aggregation and batch processing" << endl;
cout << endl;
}
void demonstrateSystemError() {
cout << "=== std::system_error (C++11) ===" << endl;
// system_error for operating system errors
// 1. Basic system_error usage
try {
ifstream file("nonexistent.txt");
file.exceptions(ios::failbit);
string line;
getline(file, line);
}
catch (const system_error& e) {
cout << "System error: " << e.what() << endl;
cout << "Error code: " << e.code() << endl;
cout << "Error value: " << e.code().value() << endl;
cout << "Error category: " << e.code().category().name() << endl;
cout << "Error message: " << e.code().message() << endl;
}
// 2. Creating custom system_error
enum class NetworkError {
connection_failed = 1,
timeout = 2,
protocol_error = 3
};
class NetworkErrorCategory : public error_category {
public:
const char* name() const noexcept override {
return "network";
}
string message(int ev) const override {
switch (static_cast<NetworkError>(ev)) {
case NetworkError::connection_failed:
return "Connection failed";
case NetworkError::timeout:
return "Connection timeout";
case NetworkError::protocol_error:
return "Protocol error";
default:
return "Unknown network error";
}
}
};
const NetworkErrorCategory network_error_category{};
error_code make_error_code(NetworkError e) {
return {static_cast<int>(e), network_error_category};
}
// 3. Using custom error codes
void connectToServer() {
// Simulate network error
throw system_error(make_error_code(NetworkError::timeout),
"Failed to connect to server");
}
try {
connectToServer();
}
catch (const system_error& e) {
cout << "\nNetwork error: " << e.what() << endl;
if (e.code().category() == network_error_category) {
cout << "Network error code: " << e.code().value() << endl;
cout << "Network error message: " << e.code().message() << endl;
}
}
// 4. Error conditions
cout << "\nError conditions:" << endl;
error_code ec = make_error_code(NetworkError::timeout);
error_condition cond = ec.default_error_condition();
cout << "Error code: " << ec.value() << endl;
cout << "Error condition: " << cond.value() << endl;
cout << "Condition category: " << cond.category().name() << endl;
cout << "\nBenefits of system_error:" << endl;
cout << "1. Standard way to handle system/OS errors" << endl;
cout << "2. Extensible with custom error categories" << endl;
cout << "3. Portable error code handling" << endl;
cout << "4. Integration with std::error_code and std::error_condition" << endl;
cout << endl;
}
void demonstrateFutureExceptions() {
cout << "=== Exception Handling with std::future (C++11) ===" << endl;
// Futures can propagate exceptions from async operations
// 1. Basic future exception handling
auto computeValue = []() -> int {
this_thread::sleep_for(chrono::milliseconds(50));
throw runtime_error("Computation failed");
return 42;
};
future<int> fut = async(launch::async, computeValue);
try {
int result = fut.get(); // Will throw exception from computeValue
cout << "Result: " << result << endl;
}
catch (const exception& e) {
cout << "Exception from future: " << e.what() << endl;
}
// 2. Multiple futures with exception handling
cout << "\nMultiple futures:" << endl;
vector<future<int>> futures;
for (int i = 0; i < 5; ++i) {
futures.push_back(async(launch::async, [i]() -> int {
if (i == 2) {
throw runtime_error("Task " + to_string(i) + " failed");
}
return i * 10;
}));
}
for (auto& f : futures) {
try {
int result = f.get();
cout << "Task succeeded: " << result << endl;
}
catch (const exception& e) {
cout << "Task failed: " << e.what() << endl;
}
}
// 3. promise for explicit exception setting
cout << "\nUsing promise to set exceptions:" << endl;
promise<int> prom;
future<int> fut2 = prom.get_future();
// Thread that might fail
thread worker([&prom]() {
try {
// Simulate work
this_thread::sleep_for(chrono::milliseconds(100));
throw logic_error("Worker thread failed");
// If success:
// prom.set_value(42);
}
catch (...) {
// Set exception on promise
prom.set_exception(current_exception());
}
});
try {
int result = fut2.get();
cout << "Worker result: " << result << endl;
}
catch (const exception& e) {
cout << "Worker failed: " << e.what() << endl;
}
worker.join();
// 4. shared_future for multiple readers
cout << "\nshared_future for multiple exception handlers:" << endl;
promise<int> prom2;
shared_future<int> shared_fut = prom2.get_future().share();
// Multiple threads can wait on the same shared_future
vector<thread> readers;
for (int i = 0; i < 3; ++i) {
readers.emplace_back([shared_fut, i]() {
try {
int result = shared_fut.get();
cout << "Reader " << i << " got: " << result << endl;
}
catch (const exception& e) {
cout << "Reader " << i << " caught: " << e.what() << endl;
}
});
}
// Set exception (or value) on promise
prom2.set_exception(make_exception_ptr(runtime_error("Shared computation failed")));
for (auto& t : readers) {
t.join();
}
cout << "\nFuture exception handling benefits:" << endl;
cout << "1. Exception propagation across threads" << endl;
cout << "2. Asynchronous error handling" << endl;
cout << "3. Clean separation of error production and consumption" << endl;
cout << "4. Support for multiple exception consumers" << endl;
cout << endl;
}
void demonstrateExceptionHandlingStrategies() {
cout << "=== Exception Handling Strategies ===" << endl;
// Different strategies for different scenarios
cout << "1. Let it throw (propagate to caller):" << endl;
cout << " - When caller can handle error meaningfully" << endl;
cout << " - In library code without UI" << endl;
cout << " - When error recovery is possible at higher level" << endl;
cout << "\n2. Catch and translate:" << endl;
cout << " - Convert low-level exceptions to high-level" << endl;
cout << " - Add context information" << endl;
cout << " - Use nested exceptions for stack traces" << endl;
cout << "\n3. Catch and recover:" << endl;
cout << " - When alternative path exists" << endl;
cout << " - For non-critical failures" << endl;
cout << " - With logging of the incident" << endl;
cout << "\n4. Catch and rethrow:" << endl;
cout << " - Cleanup resources before rethrowing" << endl;
cout << " - Add logging or telemetry" << endl;
cout << " - Transform exception type if needed" << endl;
cout << "\n5. Catch and terminate:" << endl;
cout << " - For unrecoverable errors" << endl;
cout << " - When program state is corrupted" << endl;
cout << " - In destructors (must not throw)" << endl;
// Example: Strategy pattern for exception handling
class ExceptionHandler {
public:
virtual ~ExceptionHandler() = default;
virtual void handle(exception_ptr eptr) = 0;
};
class LogAndRethrowHandler : public ExceptionHandler {
public:
void handle(exception_ptr eptr) override {
try {
rethrow_exception(eptr);
}
catch (const exception& e) {
cerr << "ERROR: " << e.what() << endl;
// Log to file, send to monitoring, etc.
throw;
}
}
};
class TranslateAndThrowHandler : public ExceptionHandler {
public:
void handle(exception_ptr eptr) override {
try {
rethrow_exception(eptr);
}
catch (const system_error& e) {
// Translate system errors to application errors
throw runtime_error("System operation failed: " + string(e.what()));
}
catch (const exception& e) {
// Wrap other exceptions
throw_with_nested(
runtime_error("Operation failed: " + string(e.what()))
);
}
}
};
cout << "\nChoosing a strategy depends on:" << endl;
cout << "- Application type (CLI, GUI, server, library)" << endl;
cout << "- Error criticality" << endl;
cout << "- User experience requirements" << endl;
cout << "- Logging and monitoring infrastructure" << endl;
cout << "- Team conventions and coding standards" << endl;
cout << endl;
}
void demonstrateErrorHandlingAlternatives() {
cout << "=== Alternatives to Exceptions ===" << endl;
// Sometimes exceptions aren't the right choice
cout << "1. Return error codes:" << endl;
cout << " - For performance-critical code" << endl;
cout << " - When exceptions are disabled" << endl;
cout << " - In C interfaces or callbacks" << endl;
enum class ErrorCode {
Success = 0,
FileNotFound,
PermissionDenied,
InvalidFormat,
OutOfMemory
};
ErrorCode readFile(const string& filename, string& content) {
ifstream file(filename);
if (!file) return ErrorCode::FileNotFound;
// ... read file
return ErrorCode::Success;
}
cout << "\n2. std::optional (C++17):" << endl;
cout << " - For operations that might not produce a result" << endl;
cout << " - When 'no value' is a valid outcome" << endl;
optional<int> safeDivide(int a, int b) {
if (b == 0) return nullopt;
return a / b;
}
cout << "\n3. std::expected (C++23):" << endl;
cout << " - Combines value and error information" << endl;
cout << " - Like std::optional but with error details" << endl;
cout << "\n4. Assertions:" << endl;
cout << " - For programming errors (bugs)" << endl;
cout << " - During development and testing" << endl;
cout << " - Often disabled in release builds" << endl;
#define CHECK(condition, message) \
if (!(condition)) { \
cerr << "Assertion failed: " << #condition << " - " << message << endl; \
abort(); \
}
cout << "\n5. When NOT to use exceptions:" << endl;
cout << "- In destructors" << endl;
cout << "- Across module boundaries (e.g., DLLs)" << endl;
cout << "- In performance-critical inner loops" << endl;
cout << "- When exception support is disabled" << endl;
cout << "- For control flow (exceptions are for exceptional conditions)" << endl;
cout << "\n6. Mixed approach:" << endl;
cout << "- Use exceptions for unexpected errors" << endl;
cout << "- Use error codes for expected failures" << endl;
cout << "- Use assertions for invariants and preconditions" << endl;
cout << "- Document which approach each function uses" << endl;
cout << endl;
}
int main() {
demonstrateNestedExceptions();
demonstrateExceptionPtr();
demonstrateSystemError();
demonstrateFutureExceptions();
demonstrateExceptionHandlingStrategies();
demonstrateErrorHandlingAlternatives();
return 0;
}
- Exceptions: For unexpected, recoverable errors across multiple layers
- Error codes: For expected failures in performance-critical code
- std::optional: When "no value" is a valid, expected result
- Assertions: For programming errors that should never occur
- Termination: For unrecoverable errors where continuing is dangerous
- Logging: Always log exceptions before handling (for debugging)
6. Exception Handling Best Practices
Best Practices
- Throw by value, catch by const reference
- Use RAII for all resource management
- Mark destructors as
noexcept - Provide at least basic exception safety
- Document exception guarantees
- Use standard exceptions when appropriate
- Catch exceptions at appropriate level
Common Mistakes
- Empty catch blocks (swallowing exceptions)
- Throwing exceptions from destructors
- Catching exceptions by value (object slicing)
- Using exceptions for normal control flow
- Not cleaning up resources in exception paths
- Incorrect catch block order
- Forgetting to mark move operations as
noexcept
Critical Rules (Never Violate)
- NEVER throw from destructors - causes
std::terminate() - NEVER let exceptions escape noexcept functions - causes
std::terminate() - ALWAYS provide basic exception safety - no resource leaks
- ALWAYS use RAII for resource management
- NEVER use exceptions across module/DLL boundaries unless explicitly designed for it
#include <iostream>
#include <memory>
#include <vector>
#include <stdexcept>
using namespace std;
// BAD PRACTICE examples
void demonstrateBadPractices() {
cout << "=== BAD PRACTICES ===" << endl;
// 1. Empty catch block (swallowing exceptions)
try {
throw runtime_error("Important error!");
}
catch (...) {
// BAD: Swallowing exception - no one knows it happened!
}
// 2. Catching by value (object slicing)
try {
throw runtime_error("Error message");
}
catch (exception e) { // BAD: Slices the exception object
cout << "Caught: " << e.what() << endl;
}
// 3. Throwing from destructor
class BadDestructor {
public:
~BadDestructor() {
throw runtime_error("Exception in destructor"); // VERY BAD!
}
};
// 4. Manual resource management without RAII
void leakyFunction() {
int* resource = new int(42);
if (/* some condition */) {
throw runtime_error("Error");
// BAD: Memory leak - delete never called
}
delete resource;
}
// 5. Using exceptions for normal control flow
void findElement(const vector<int>& v, int value) {
try {
for (size_t i = 0; i < v.size(); ++i) {
if (v[i] == value) {
throw i; // BAD: Using exception for normal flow
}
}
throw -1;
}
catch (size_t index) {
cout << "Found at index " << index << endl;
}
catch (int) {
cout << "Not found" << endl;
}
}
// 6. Not providing exception safety
class UnsafeVector {
private:
int* data;
size_t size;
size_t capacity;
public:
void push_back(int value) {
if (size == capacity) {
// Reallocate without preserving old data on exception
int* new_data = new int[capacity * 2]; // Might throw
// If exception here, old data is lost!
delete[] data;
data = new_data;
capacity *= 2;
}
data[size++] = value;
}
// BAD: No exception safety guarantee
};
cout << endl;
}
// GOOD PRACTICE examples
void demonstrateGoodPractices() {
cout << "=== GOOD PRACTICES ===" << endl;
// 1. Proper catch block
try {
throw runtime_error("Important error!");
}
catch (const exception& e) { // GOOD: Catch by const reference
cerr << "Error logged: " << e.what() << endl;
// Handle or rethrow appropriately
}
// 2. RAII for resource management
void safeFunction() {
unique_ptr<int> resource = make_unique<int>(42);
if (/* some condition */) {
throw runtime_error("Error");
// GOOD: resource automatically deleted by unique_ptr destructor
}
// Use resource...
}
// 3. Proper destructor
class GoodDestructor {
public:
~GoodDestructor() noexcept { // GOOD: noexcept destructor
// Cleanup that won't throw
}
};
// 4. Exception safety guarantees
class SafeVector {
private:
unique_ptr<int[]> data;
size_t size;
size_t capacity;
void reallocate(size_t new_capacity) {
unique_ptr<int[]> new_data(new int[new_capacity]);
// Copy old data
for (size_t i = 0; i < size; ++i) {
new_data[i] = data[i];
}
// Commit changes (non-throwing operations only)
data = move(new_data);
capacity = new_capacity;
}
public:
// Strong exception safety guarantee
void push_back(int value) {
if (size == capacity) {
reallocate(capacity == 0 ? 1 : capacity * 2);
}
data[size++] = value;
}
};
// 5. Using proper control flow
optional<size_t> findElementGood(const vector<int>& v, int value) {
for (size_t i = 0; i < v.size(); ++i) {
if (v[i] == value) {
return i; // GOOD: Return value, not exception
}
}
return nullopt; // GOOD: Clear "not found" indication
}
// 6. noexcept where appropriate
class EfficientClass {
public:
EfficientClass() noexcept = default;
~EfficientClass() noexcept = default;
// Move operations should be noexcept
EfficientClass(EfficientClass&&) noexcept = default;
EfficientClass& operator=(EfficientClass&&) noexcept = default;
// Copy operations
EfficientClass(const EfficientClass&) = default;
EfficientClass& operator=(const EfficientClass&) = default;
};
// 7. Proper exception hierarchy
class FileSystemException : public runtime_error {
public:
FileSystemException(const string& msg, const string& path)
: runtime_error(msg + ": " + path), filepath(path) {}
const string& getFilePath() const { return filepath; }
private:
string filepath;
};
class FileNotFoundException : public FileSystemException {
public:
FileNotFoundException(const string& path)
: FileSystemException("File not found", path) {}
};
class PermissionDeniedException : public FileSystemException {
public:
PermissionDeniedException(const string& path)
: FileSystemException("Permission denied", path) {}
};
// 8. Cleanup with RAII even in catch blocks
void processWithCleanup() {
auto resource = make_unique<int>(42);
try {
// Operations that might throw
throw runtime_error("Operation failed");
}
catch (...) {
// Resource still automatically cleaned up
// Additional cleanup if needed
throw; // Rethrow after cleanup
}
}
// 9. Documenting exception guarantees
/**
* @brief Performs calculation
* @param input Input value
* @return Calculation result
* @throws invalid_argument if input is negative
* @throws overflow_error if result exceeds maximum value
*
* Exception safety: Strong guarantee
*/
int calculate(int input) {
if (input < 0) {
throw invalid_argument("Input must be non-negative");
}
// Calculation...
return input * 2;
}
// 10. Using standard exceptions when appropriate
void validateAge(int age) {
if (age < 0) {
throw invalid_argument("Age cannot be negative"); // GOOD: Standard exception
}
if (age > 150) {
throw out_of_range("Age is not realistic"); // GOOD: Standard exception
}
}
cout << "\nSummary of good practices:" << endl;
cout << "1. Use RAII for all resources" << endl;
cout << "2. Throw by value, catch by const reference" << endl;
cout << "3. Never throw from destructors" << endl;
cout << "4. Mark destructors as noexcept" << endl;
cout << "5. Document exception guarantees" << endl;
cout << "6. Provide at least basic exception safety" << endl;
cout << "7. Use standard exceptions when they fit" << endl;
cout << "8. Catch at appropriate level of abstraction" << endl;
cout << "9. Don't use exceptions for normal control flow" << endl;
cout << "10. Log exceptions before handling (when appropriate)" << endl;
cout << endl;
}
void demonstratePerformanceConsiderations() {
cout << "=== Performance Considerations ===" << endl;
cout << "Exception handling has overhead:" << endl;
cout << "1. No overhead when no exceptions are thrown" << endl;
cout << "2. Stack unwinding is expensive when exceptions occur" << endl;
cout << "3. Code size increases due to exception tables" << endl;
cout << "\nWhen exceptions affect performance:" << endl;
cout << "- In tight loops with frequent throwing" << endl;
cout << "- Real-time systems with strict timing requirements" << endl;
cout << "- Performance-critical library code" << endl;
cout << "\nOptimization tips:" << endl;
cout << "1. Use noexcept for functions that won't throw" << endl;
cout << "2. Avoid exceptions in performance-critical paths" << endl;
cout << "3. Consider error codes for high-frequency operations" << endl;
cout << "4. Profile to identify actual performance issues" << endl;
cout << "\nRemember: Exceptions are for EXCEPTIONAL conditions" << endl;
cout << "They shouldn't be part of normal program flow" << endl;
cout << endl;
}
int main() {
demonstrateBadPractices();
demonstrateGoodPractices();
demonstratePerformanceConsiderations();
return 0;
}
Exception Safety Guarantees Comparison
The following table compares different exception safety guarantees with their characteristics and implementation requirements:
| Safety Level | Guarantee | Characteristics | Implementation Requirements |
|---|---|---|---|
| No-throw Guarantee | Operation never throws exceptions | Strongest guarantee, marked with noexcept |
All operations must be non-throwing |
| Strong Guarantee | Commit-or-rollback semantics | Operation succeeds completely or has no effect | Copy-and-swap idiom, operations on temporaries |
| Basic Guarantee | No resource leaks, valid state | Objects remain valid but unspecified state | RAII for resources, destructor cleanup |
| No Guarantee | No promises | Resource leaks possible, invalid state possible | Avoid! Never acceptable in production code |
- All functions should provide at least basic guarantee
- Destructors, move operations, and swap should be noexcept
- Key operations should provide strong guarantee
- Document the guarantee level for each function
- Test exception safety with exception injection