C++ Exception Handling
Overview
Exception handling provides a way to deal with runtime errors and exceptional situations in a structured manner.
Basic Exception Handling
try-catch Blocks
cpp
#include <iostream>
#include <stdexcept>
int main() {
try {
int numerator = 10;
int denominator = 0;
if (denominator == 0) {
throw std::runtime_error("Division by zero!");
}
double result = numerator / denominator;
std::cout << "Result: " << result << std::endl;
} catch (const std::runtime_error& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}Multiple catch Blocks
cpp
#include <iostream>
#include <stdexcept>
#include <vector>
int main() {
try {
std::vector<int> vec = {1, 2, 3};
// This will throw std::out_of_range
int value = vec.at(10);
} catch (const std::out_of_range& e) {
std::cout << "Out of range error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "Standard exception: " << e.what() << std::endl;
} catch (...) {
std::cout << "Unknown exception occurred" << std::endl;
}
return 0;
}Custom Exceptions
Creating Custom Exception Classes
cpp
#include <iostream>
#include <exception>
#include <string>
// Custom exception class
class InsufficientFundsException : public std::exception {
private:
std::string message;
public:
InsufficientFundsException(double balance, double amount) {
message = "Insufficient funds. Balance: " + std::to_string(balance) +
", Attempted: " + std::to_string(amount);
}
const char* what() const noexcept override {
return message.c_str();
}
};
class BankAccount {
private:
double balance;
public:
BankAccount(double initial_balance) : balance(initial_balance) {}
void withdraw(double amount) {
if (amount > balance) {
throw InsufficientFundsException(balance, amount);
}
balance -= amount;
std::cout << "Withdrawal successful. New balance: " << balance << std::endl;
}
double getBalance() const { return balance; }
};
int main() {
try {
BankAccount account(100.0);
account.withdraw(50.0);
account.withdraw(75.0); // This will throw exception
} catch (const InsufficientFundsException& e) {
std::cout << "Transaction failed: " << e.what() << std::endl;
}
return 0;
}Exception Safety
RAII and Exception Safety
cpp
#include <iostream>
#include <memory>
#include <fstream>
class Resource {
private:
int* data;
public:
Resource(int size) : data(new int[size]) {
std::cout << "Resource allocated" << std::endl;
}
~Resource() {
delete[] data;
std::cout << "Resource deallocated" << std::endl;
}
void processData() {
throw std::runtime_error("Error during processing");
}
};
int main() {
try {
Resource res(100);
res.processData();
} catch (const std::exception& e) {
std::cout << "Exception caught: " << e.what() << std::endl;
// Resource destructor is automatically called here
}
return 0;
}Smart Pointers and Exception Safety
cpp
#include <iostream>
#include <memory>
#include <vector>
void processData() {
// Using unique_ptr for automatic cleanup
auto data = std::make_unique<std::vector<int>>(1000);
// Fill the vector
for (int i = 0; i < 1000; i++) {
data->push_back(i);
}
// Simulate an exception
throw std::runtime_error("Processing failed");
// No need for manual cleanup - unique_ptr handles it
}
int main() {
try {
processData();
} catch (const std::exception& e) {
std::cout << "Exception caught: " << e.what() << std::endl;
std::cout << "Memory was automatically cleaned up" << std::endl;
}
return 0;
}noexcept Specifier
cpp
#include <iostream>
#include <stdexcept>
// Function that doesn't throw exceptions
int safeDivide(int a, int b) noexcept {
return a / b; // Undefined behavior if b == 0
}
// Function that might throw exceptions
int unsafeDivide(int a, int b) {
if (b == 0) {
throw std::runtime_error("Division by zero");
}
return a / b;
}
int main() {
try {
int result1 = safeDivide(10, 2);
std::cout << "Safe division result: " << result1 << std::endl;
int result2 = unsafeDivide(10, 0);
} catch (const std::exception& e) {
std::cout << "Exception caught: " << e.what() << std::endl;
}
return 0;
}Best Practices
Exception Handling Guidelines
cpp
#include <iostream>
#include <stdexcept>
#include <vector>
class Database {
private:
std::vector<std::string> records;
public:
void addRecord(const std::string& record) {
if (record.empty()) {
throw std::invalid_argument("Record cannot be empty");
}
records.push_back(record);
}
std::string getRecord(size_t index) const {
if (index >= records.size()) {
throw std::out_of_range("Record index out of range");
}
return records[index];
}
size_t getRecordCount() const noexcept {
return records.size();
}
};
int main() {
Database db;
try {
db.addRecord("First record");
db.addRecord("Second record");
db.addRecord(""); // This will throw an exception
} catch (const std::invalid_argument& e) {
std::cout << "Invalid argument: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "Standard exception: " << e.what() << std::endl;
}
// Check record count (noexcept function)
std::cout << "Record count: " << db.getRecordCount() << std::endl;
return 0;
}