异常处理是 C++ 中处理运行时错误的机制,它允许程序在遇到错误时优雅地处理,而不是直接崩溃。异常处理使用 try、catch 和 throw 关键字来实现。

异常处理的基本概念

try-catch 基础

#include <iostream>
#include <stdexcept>
#include <string>
 
// 可能抛出异常的函数
int divide(int a, int b) {
    if (b == 0) {
        throw std::runtime_error("除数不能为零");
    }
    return a / b;
}
 
double squareRoot(double x) {
    if (x < 0) {
        throw std::invalid_argument("不能计算负数的平方根");
    }
    return sqrt(x);
}
 
int main() {
    std::cout << "=== 异常处理基础 ===" << std::endl;
    
    // 1. 基本的 try-catch
    try {
        int result = divide(10, 2);
        std::cout << "10 / 2 = " << result << std::endl;
        
        result = divide(10, 0);  // 这会抛出异常
        std::cout << "这行不会执行" << std::endl;
    }
    catch (const std::runtime_error& e) {
        std::cout << "捕获到运行时错误: " << e.what() << std::endl;
    }
    
    // 2. 多个 catch 块
    try {
        double result = squareRoot(-4.0);
        std::cout << "sqrt(-4) = " << result << std::endl;
    }
    catch (const std::invalid_argument& e) {
        std::cout << "捕获到无效参数错误: " << e.what() << std::endl;
    }
    catch (const std::exception& e) {
        std::cout << "捕获到通用异常: " << e.what() << std::endl;
    }
    
    // 3. 捕获所有异常
    try {
        throw 42;  // 抛出整数
    }
    catch (...) {
        std::cout << "捕获到未知类型的异常" << std::endl;
    }
    
    std::cout << "程序继续执行" << std::endl;
    
    return 0;
}

异常的传播

#include <iostream>
#include <stdexcept>
 
void level3() {
    std::cout << "进入 level3" << std::endl;
    throw std::runtime_error("level3 中的错误");
    std::cout << "level3 结束(不会执行)" << std::endl;
}
 
void level2() {
    std::cout << "进入 level2" << std::endl;
    level3();  // 异常会从这里传播出去
    std::cout << "level2 结束(不会执行)" << std::endl;
}
 
void level1() {
    std::cout << "进入 level1" << std::endl;
    try {
        level2();
    }
    catch (const std::exception& e) {
        std::cout << "在 level1 中捕获异常: " << e.what() << std::endl;
    }
    std::cout << "level1 结束" << std::endl;
}
 
// 演示栈展开
class StackUnwindDemo {
public:
    std::string name;
    
    StackUnwindDemo(const std::string& n) : name(n) {
        std::cout << "构造 " << name << std::endl;
    }
    
    ~StackUnwindDemo() {
        std::cout << "析构 " << name << std::endl;
    }
};
 
void demonstrateStackUnwind() {
    std::cout << "\n=== 栈展开演示 ===" << std::endl;
    
    StackUnwindDemo obj1("对象1");
    
    try {
        StackUnwindDemo obj2("对象2");
        {
            StackUnwindDemo obj3("对象3");
            throw std::runtime_error("测试栈展开");
        }
    }
    catch (const std::exception& e) {
        std::cout << "捕获异常: " << e.what() << std::endl;
    }
    
    std::cout << "函数结束" << std::endl;
}
 
int main() {
    std::cout << "=== 异常传播演示 ===" << std::endl;
    level1();
    
    demonstrateStackUnwind();
    
    return 0;
}

标准异常类型

异常类层次结构

#include <iostream>
#include <stdexcept>
#include <typeinfo>
#include <vector>
#include <new>
 
void demonstrateStandardExceptions() {
    std::cout << "=== 标准异常类型演示 ===" << std::endl;
    
    // 1. std::logic_error 及其派生类
    try {
        std::vector<int> vec(5);
        int value = vec.at(10);  // 越界访问
    }
    catch (const std::out_of_range& e) {
        std::cout << "out_of_range: " << e.what() << std::endl;
    }
    
    try {
        throw std::invalid_argument("无效的参数值");
    }
    catch (const std::invalid_argument& e) {
        std::cout << "invalid_argument: " << e.what() << std::endl;
    }
    
    try {
        throw std::domain_error("数学域错误");
    }
    catch (const std::domain_error& e) {
        std::cout << "domain_error: " << e.what() << std::endl;
    }
    
    try {
        throw std::length_error("长度错误");
    }
    catch (const std::length_error& e) {
        std::cout << "length_error: " << e.what() << std::endl;
    }
    
    // 2. std::runtime_error 及其派生类
    try {
        throw std::runtime_error("运行时错误");
    }
    catch (const std::runtime_error& e) {
        std::cout << "runtime_error: " << e.what() << std::endl;
    }
    
    try {
        throw std::range_error("范围错误");
    }
    catch (const std::range_error& e) {
        std::cout << "range_error: " << e.what() << std::endl;
    }
    
    try {
        throw std::overflow_error("溢出错误");
    }
    catch (const std::overflow_error& e) {
        std::cout << "overflow_error: " << e.what() << std::endl;
    }
    
    try {
        throw std::underflow_error("下溢错误");
    }
    catch (const std::underflow_error& e) {
        std::cout << "underflow_error: " << e.what() << std::endl;
    }
    
    // 3. 其他标准异常
    try {
        throw std::bad_alloc();  // 内存分配失败
    }
    catch (const std::bad_alloc& e) {
        std::cout << "bad_alloc: " << e.what() << std::endl;
    }
    
    try {
        throw std::bad_cast();  // 类型转换失败
    }
    catch (const std::bad_cast& e) {
        std::cout << "bad_cast: " << e.what() << std::endl;
    }
}
 
// 演示异常类型的继承关系
void demonstrateExceptionHierarchy() {
    std::cout << "\n=== 异常继承关系演示 ===" << std::endl;
    
    try {
        throw std::out_of_range("越界错误");
    }
    catch (const std::logic_error& e) {  // 捕获基类
        std::cout << "捕获为 logic_error: " << e.what() << std::endl;
        std::cout << "实际类型: " << typeid(e).name() << std::endl;
    }
    
    try {
        throw std::overflow_error("溢出错误");
    }
    catch (const std::exception& e) {  // 捕获最基础的异常类
        std::cout << "捕获为 exception: " << e.what() << std::endl;
        std::cout << "实际类型: " << typeid(e).name() << std::endl;
    }
}
 
int main() {
    demonstrateStandardExceptions();
    demonstrateExceptionHierarchy();
    
    return 0;
}

自定义异常类

#include <iostream>
#include <stdexcept>
#include <string>
 
// 1. 继承自标准异常类的自定义异常
class MathError : public std::runtime_error {
public:
    MathError(const std::string& message) 
        : std::runtime_error("数学错误: " + message) {}
};
 
class DivisionByZeroError : public MathError {
public:
    DivisionByZeroError() 
        : MathError("除数不能为零") {}
};
 
class NegativeSquareRootError : public MathError {
private:
    double value;
    
public:
    NegativeSquareRootError(double val) 
        : MathError("不能计算负数的平方根"), value(val) {}
    
    double getValue() const { return value; }
};
 
// 2. 完全自定义的异常类
class FileError {
private:
    std::string filename;
    std::string operation;
    int errorCode;
    
public:
    FileError(const std::string& file, const std::string& op, int code)
        : filename(file), operation(op), errorCode(code) {}
    
    const std::string& getFilename() const { return filename; }
    const std::string& getOperation() const { return operation; }
    int getErrorCode() const { return errorCode; }
    
    std::string what() const {
        return "文件操作错误: " + operation + " 文件 '" + filename + 
               "' 失败,错误代码: " + std::to_string(errorCode);
    }
};
 
// 3. 带有更多信息的异常类
class NetworkError : public std::exception {
private:
    std::string host;
    int port;
    std::string message;
    mutable std::string fullMessage;  // mutable 允许在 const 函数中修改
    
public:
    NetworkError(const std::string& h, int p, const std::string& msg)
        : host(h), port(p), message(msg) {}
    
    const char* what() const noexcept override {
        fullMessage = "网络错误 [" + host + ":" + std::to_string(port) + "] " + message;
        return fullMessage.c_str();
    }
    
    const std::string& getHost() const { return host; }
    int getPort() const { return port; }
    const std::string& getMessage() const { return message; }
};
 
// 使用自定义异常的函数
double safeDivide(double a, double b) {
    if (b == 0.0) {
        throw DivisionByZeroError();
    }
    return a / b;
}
 
double safeSquareRoot(double x) {
    if (x < 0) {
        throw NegativeSquareRootError(x);
    }
    return sqrt(x);
}
 
void simulateFileOperation(const std::string& filename) {
    // 模拟文件操作失败
    throw FileError(filename, "读取", 404);
}
 
void simulateNetworkOperation(const std::string& host, int port) {
    // 模拟网络连接失败
    throw NetworkError(host, port, "连接超时");
}
 
int main() {
    std::cout << "=== 自定义异常演示 ===" << std::endl;
    
    // 1. 测试数学异常
    try {
        double result = safeDivide(10.0, 0.0);
    }
    catch (const DivisionByZeroError& e) {
        std::cout << "捕获除零错误: " << e.what() << std::endl;
    }
    
    try {
        double result = safeSquareRoot(-4.0);
    }
    catch (const NegativeSquareRootError& e) {
        std::cout << "捕获负数平方根错误: " << e.what() << std::endl;
        std::cout << "错误值: " << e.getValue() << std::endl;
    }
    
    // 2. 测试文件异常
    try {
        simulateFileOperation("nonexistent.txt");
    }
    catch (const FileError& e) {
        std::cout << "捕获文件错误: " << e.what() << std::endl;
        std::cout << "文件名: " << e.getFilename() << std::endl;
        std::cout << "操作: " << e.getOperation() << std::endl;
        std::cout << "错误代码: " << e.getErrorCode() << std::endl;
    }
    
    // 3. 测试网络异常
    try {
        simulateNetworkOperation("example.com", 8080);
    }
    catch (const NetworkError& e) {
        std::cout << "捕获网络错误: " << e.what() << std::endl;
        std::cout << "主机: " << e.getHost() << std::endl;
        std::cout << "端口: " << e.getPort() << std::endl;
    }
    
    // 4. 使用基类捕获
    try {
        throw NegativeSquareRootError(-9.0);
    }
    catch (const MathError& e) {  // 基类捕获
        std::cout << "通过基类捕获: " << e.what() << std::endl;
    }
    
    return 0;
}

异常安全性

RAII 和异常安全

什么是RAII?

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是C++中一种重要的编程习惯,它将资源的生命周期绑定到对象的生命周期上。当对象被构造时获取资源,当对象被析构时释放资源。

异常安全级别

  1. 基本安全保证:程序状态保持有效,不会泄漏资源
  2. 强安全保证:操作要么完全成功,要么完全失败,程序状态不变
  3. 不抛异常保证:操作保证不会抛出异常

代码示例

#include <iostream>
#include <memory>
#include <fstream>
#include <vector>
#include <stdexcept>
 
// 不安全的资源管理
class UnsafeResource {
private:
    int* data;
    
public:
    UnsafeResource(int size) {
        data = new int[size];
        std::cout << "分配资源" << std::endl;
        
        // 如果这里抛出异常,内存泄漏!
        if (size < 0) {
            throw std::invalid_argument("大小不能为负数");
        }
    }
    
    ~UnsafeResource() {
        delete[] data;
        std::cout << "释放资源" << std::endl;
    }
};
 
// 安全的资源管理(RAII)
class SafeResource {
private:
    std::unique_ptr<int[]> data;
    int size_;
    
public:
    SafeResource(int size) : size_(size) {
        // 先验证参数,再分配资源
        if (size < 0) {
            throw std::invalid_argument("大小不能为负数");
        }
        
        data = std::make_unique<int[]>(size);
        std::cout << "安全分配资源,大小: " << size << std::endl;
    }
    
    // 提供访问接口
    int& operator[](int index) {
        if (index < 0 || index >= size_) {
            throw std::out_of_range("索引超出范围");
        }
        return data[index];
    }
    
    int size() const noexcept { return size_; }
    
    // 移动构造函数(强异常安全)
    SafeResource(SafeResource&& other) noexcept 
        : data(std::move(other.data)), size_(other.size_) {
        other.size_ = 0;
        std::cout << "移动构造" << std::endl;
    }
    
    // 移动赋值运算符
    SafeResource& operator=(SafeResource&& other) noexcept {
        if (this != &other) {
            data = std::move(other.data);
            size_ = other.size_;
            other.size_ = 0;
            std::cout << "移动赋值" << std::endl;
        }
        return *this;
    }
    
    // 删除拷贝构造和拷贝赋值(或实现强异常安全版本)
    SafeResource(const SafeResource&) = delete;
    SafeResource& operator=(const SafeResource&) = delete;
    
    ~SafeResource() {
        if (data) {
            std::cout << "安全释放资源" << std::endl;
        }
    }
};
 
// 文件RAII包装器
class SafeFile {
private:
    std::unique_ptr<std::FILE, decltype(&std::fclose)> file_;
    
public:
    SafeFile(const char* filename, const char* mode) 
        : file_(nullptr, &std::fclose) {
        std::FILE* f = std::fopen(filename, mode);
        if (!f) {
            throw std::runtime_error("无法打开文件");
        }
        file_.reset(f);
        std::cout << "文件打开: " << filename << std::endl;
    }
    
    std::FILE* get() const noexcept { return file_.get(); }
    
    ~SafeFile() {
        if (file_) {
            std::cout << "文件关闭" << std::endl;
        }
    }
};
 
// 强异常安全的vector操作
class SafeVector {
private:
    std::vector<int> data_;
    
public:
    // 强异常安全的push_back
    void safe_push_back(int value) {
        // vector的push_back已经提供强异常安全保证
        data_.push_back(value);
    }
    
    // 强异常安全的批量添加
    void add_multiple(const std::vector<int>& values) {
        // 使用临时对象确保强异常安全
        auto temp = data_;  // 复制当前状态
        try {
            temp.insert(temp.end(), values.begin(), values.end());
            data_ = std::move(temp);  // 只有成功时才更新
        } catch (...) {
            // 如果出现异常,data_保持原状
            throw;
        }
    }
    
    size_t size() const noexcept { return data_.size(); }
    
    void print() const {
        std::cout << "Vector内容: ";
        for (const auto& item : data_) {
            std::cout << item << " ";
        }
        std::cout << std::endl;
    }
};
 
// 多资源管理示例
class MultiResourceManager {
private:
    std::unique_ptr<int[]> buffer_;
    std::unique_ptr<SafeFile> file_;
    std::vector<int> data_;
    
public:
    MultiResourceManager(int buffer_size, const char* filename) {
        // 按顺序获取资源,如果任何一个失败,已获取的资源会自动释放
        
        if (buffer_size <= 0) {
            throw std::invalid_argument("缓冲区大小必须为正数");
        }
        
        buffer_ = std::make_unique<int[]>(buffer_size);
        std::cout << "分配缓冲区: " << buffer_size << std::endl;
        
        file_ = std::make_unique<SafeFile>(filename, "w");
        
        // 预留空间以避免重新分配
        data_.reserve(buffer_size);
        
        std::cout << "所有资源初始化完成" << std::endl;
    }
    
    void add_data(int value) {
        data_.push_back(value);
    }
    
    ~MultiResourceManager() {
        std::cout << "清理所有资源" << std::endl;
    }
};
 
// 演示函数
void demonstrate_exception_safety() {
    std::cout << "=== 异常安全性演示 ===" << std::endl;
    
    // 1. 不安全的资源管理
    std::cout << "\n1. 不安全的资源管理:" << std::endl;
    try {
        UnsafeResource unsafe(-1);  // 这会导致内存泄漏
    } catch (const std::exception& e) {
        std::cout << "捕获异常: " << e.what() << std::endl;
    }
    
    // 2. 安全的资源管理
    std::cout << "\n2. 安全的资源管理:" << std::endl;
    try {
        SafeResource safe(-1);  // 不会泄漏内存
    } catch (const std::exception& e) {
        std::cout << "捕获异常: " << e.what() << std::endl;
    }
    
    // 3. 正常使用
    std::cout << "\n3. 正常使用:" << std::endl;
    try {
        SafeResource safe(5);
        safe[0] = 10;
        safe[1] = 20;
        std::cout << "safe[0] = " << safe[0] << std::endl;
        
        // 移动语义
        SafeResource moved = std::move(safe);
        std::cout << "移动后大小: " << moved.size() << std::endl;
    } catch (const std::exception& e) {
        std::cout << "捕获异常: " << e.what() << std::endl;
    }
    
    // 4. 文件资源管理
    std::cout << "\n4. 文件资源管理:" << std::endl;
    try {
        SafeFile file("test.txt", "w");
        std::fprintf(file.get(), "Hello, RAII!\n");
        // 文件会在作用域结束时自动关闭
    } catch (const std::exception& e) {
        std::cout << "文件操作异常: " << e.what() << std::endl;
    }
    
    // 5. 强异常安全的容器操作
    std::cout << "\n5. 强异常安全的容器操作:" << std::endl;
    SafeVector vec;
    vec.safe_push_back(1);
    vec.safe_push_back(2);
    vec.safe_push_back(3);
    vec.print();
    
    std::vector<int> to_add = {4, 5, 6, 7};
    vec.add_multiple(to_add);
    vec.print();
    
    // 6. 多资源管理
    std::cout << "\n6. 多资源管理:" << std::endl;
    try {
        MultiResourceManager manager(10, "output.txt");
        manager.add_data(42);
        // 所有资源会在析构时自动清理
    } catch (const std::exception& e) {
        std::cout << "多资源管理异常: " << e.what() << std::endl;
    }
}
 
int main() {
    demonstrate_exception_safety();
    return 0;
}

关键要点

RAII的优势

  • 自动资源管理:资源的获取和释放与对象生命周期绑定
  • 异常安全:即使抛出异常,资源也会被正确释放
  • 代码简洁:减少手动资源管理的样板代码

最佳实践

  1. 优先使用智能指针std::unique_ptr, std::shared_ptr
  2. 遵循Rule of Five:如果需要自定义析构函数,通常也需要自定义拷贝构造、拷贝赋值、移动构造、移动赋值
  3. 异常规范:构造函数可以抛异常,析构函数应该使用noexcept
  4. 资源获取顺序:在构造函数中按顺序获取资源,确保异常安全

常见陷阱

  • 构造函数中的异常:确保在抛异常前不分配资源,或使用智能指针
  • 析构函数中的异常:避免在析构函数中抛异常
  • 部分构造的对象:使用成员初始化列表和智能指针避免问题

通过遵循RAII原则,可以编写出更安全、更可靠的C++代码。