07-异常处理

异常处理是C++的错误处理机制,通过 throw/catch 实现错误传播。比返回错误码更清晰,但有性能开销。

异常基础

异常将错误检测和处理分离。throw 抛出异常,catch 捕获处理。未捕获异常导致程序终止。

异常抛出

throw 可抛出任何类型对象,但推荐使用标准异常类或其派生类。异常对象会被复制。

#include <stdexcept>

// 抛出标准异常
void divide(int a, int b) {
    if (b == 0) {
        throw std::invalid_argument("Division by zero");
    }
    return a / b;
}

// 抛出自定义异常
class CustomException : public std::exception {
private:
    std::string message;
public:
    CustomException(const std::string& msg) : message(msg) {}
    const char* what() const noexcept override {
        return message.c_str();
    }
};

void riskyFunction() {
    throw CustomException("Something went wrong");
}

异常捕获

catch 按类型匹配异常,从上到下顺序检查。派生类应在基类前,catch(...) 捕获所有异常。

try {
    divide(10, 0);
} 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;
} catch (...) {
    std::cout << "Unknown exception caught" << std::endl;
}

异常规范

noexcept 声明函数不抛异常,编译器可优化。违反noexcept会调用 std::terminate()

// C++11之前的异常规范(已废弃)
void oldFunction() throw(std::runtime_error);

// C++11的noexcept规范
void safeFunction() noexcept;  // 不抛出异常
void mayThrowFunction() noexcept(false);  // 可能抛出异常

// 条件noexcept
template<typename T>
void templateFunction(T value) noexcept(std::is_nothrow_copy_constructible_v<T>);

标准异常类

C++标准库提供异常类层次,所有标准异常继承自 std::exception。分为逻辑错误和运行时错误。

标准异常层次

标准异常形成树状继承关系,捕获基类可同时捕获所有派生类异常。

std::exception
├── std::logic_error
│   ├── std::invalid_argument
│   ├── std::domain_error
│   ├── std::length_error
│   └── std::out_of_range
├── std::runtime_error
│   ├── std::range_error
│   ├── std::overflow_error
│   ├── std::underflow_error
│   └── std::system_error
└── std::bad_alloc

常用异常类型

选择合适的异常类型传达错误信息。逻辑错误表示程序bug,运行时错误表示外部因素导致的失败。

// 逻辑错误
throw std::invalid_argument("Invalid parameter");
throw std::domain_error("Domain error");
throw std::length_error("Length error");
throw std::out_of_range("Index out of range");

// 运行时错误
throw std::range_error("Range error");
throw std::overflow_error("Overflow");
throw std::underflow_error("Underflow");
throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory));

// 内存错误
throw std::bad_alloc();

RAII与异常安全

RAII确保异常发生时资源正确释放。异常安全分三个级别:基本保证、强保证、无异常保证。

异常安全保证

异常安全描述函数在异常时的行为保证。级别越高越安全,但实现难度也越大。

class Resource {
private:
    int* data;
    size_t size;
    
public:
    // 基本异常安全
    Resource(size_t s) : size(s) {
        data = new int[size];
        // 如果这里抛出异常,析构函数不会被调用
        // 但构造函数中的分配会被清理
    }
    
    // 强异常安全
    Resource(const Resource& other) : size(other.size) {
        int* newData = new int[size];
        try {
            std::copy(other.data, other.data + size, newData);
        } catch (...) {
            delete[] newData;
            throw;  // 重新抛出异常
        }
        data = newData;
    }
    
    ~Resource() {
        delete[] data;
    }
};

智能指针与异常

#include <memory>

void safeFunction() {
    // 使用智能指针确保异常安全
    auto ptr = std::make_unique<int>(42);
    
    // 即使这里抛出异常,智能指针也会自动清理
    riskyOperation();
    
    // 正常使用
    std::cout << *ptr << std::endl;
}

异常处理最佳实践

异常安全设计

class SafeContainer {
private:
    std::vector<int> data;
    
public:
    // 强异常安全:要么成功,要么保持原状态
    void insert(int value) {
        std::vector<int> newData = data;  // 复制
        newData.push_back(value);         // 修改副本
        
        // 如果这里抛出异常,原data不受影响
        data = std::move(newData);        // 原子操作
    }
    
    // 无异常保证:使用noexcept
    void clear() noexcept {
        data.clear();
    }
};

异常传播

// 让异常自然传播
void function1() {
    throw std::runtime_error("Error in function1");
}

void function2() {
    function1();  // 异常会传播到调用者
}

// 捕获并重新抛出
void function3() {
    try {
        function1();
    } catch (const std::exception& e) {
        std::cout << "Caught in function3: " << e.what() << std::endl;
        throw;  // 重新抛出原异常
    }
}

// 抛出新异常
void function4() {
    try {
        function1();
    } catch (const std::exception& e) {
        throw std::runtime_error("Wrapped: " + std::string(e.what()));
    }
}

异常与构造函数

构造函数中的异常

class SafeClass {
private:
    std::unique_ptr<int> ptr1;
    std::unique_ptr<int> ptr2;
    
public:
    SafeClass(int value1, int value2) 
        : ptr1(std::make_unique<int>(value1)) {
        
        // 如果这里抛出异常,ptr1会被自动清理
        ptr2 = std::make_unique<int>(value2);
    }
    
    // 如果构造函数抛出异常,析构函数不会被调用
    // 但成员对象的析构函数会被调用
};

异常与析构函数

class SafeDestructor {
public:
    ~SafeDestructor() noexcept {
        try {
            // 清理资源
            cleanup();
        } catch (...) {
            // 析构函数中不应该抛出异常
            // 记录错误但不重新抛出
            std::cerr << "Error in destructor" << std::endl;
        }
    }
    
private:
    void cleanup() {
        // 可能抛出异常的清理操作
    }
};

自定义异常类

继承标准异常

class DatabaseException : public std::runtime_error {
private:
    int errorCode;
    
public:
    DatabaseException(const std::string& message, int code)
        : std::runtime_error(message), errorCode(code) {}
    
    int getErrorCode() const {
        return errorCode;
    }
    
    const char* what() const noexcept override {
        static std::string fullMessage = 
            std::runtime_error::what() + " (Code: " + std::to_string(errorCode) + ")";
        return fullMessage.c_str();
    }
};

异常链

class ChainedException : public std::exception {
private:
    std::string message;
    std::exception_ptr cause;
    
public:
    ChainedException(const std::string& msg, std::exception_ptr c = nullptr)
        : message(msg), cause(c) {}
    
    const char* what() const noexcept override {
        return message.c_str();
    }
    
    std::exception_ptr getCause() const {
        return cause;
    }
    
    void rethrowCause() const {
        if (cause) {
            std::rethrow_exception(cause);
        }
    }
};

异常处理模式

异常处理装饰器

template<typename Func>
auto withExceptionHandling(Func&& func) {
    return [func = std::forward<Func>(func)](auto&&... args) {
        try {
            return func(std::forward<decltype(args)>(args)...);
        } catch (const std::exception& e) {
            std::cerr << "Exception caught: " << e.what() << std::endl;
            throw;
        }
    };
}

// 使用
auto safeDivide = withExceptionHandling([](int a, int b) {
    if (b == 0) throw std::invalid_argument("Division by zero");
    return a / b;
});

异常安全包装器

template<typename T>
class ExceptionSafeWrapper {
private:
    T value;
    bool valid;
    
public:
    ExceptionSafeWrapper(T&& v) : value(std::move(v)), valid(true) {}
    
    template<typename Func>
    auto apply(Func&& func) -> ExceptionSafeWrapper<decltype(func(value))> {
        if (!valid) {
            throw std::runtime_error("Wrapper is invalid");
        }
        
        try {
            return ExceptionSafeWrapper<decltype(func(value))>(func(value));
        } catch (...) {
            valid = false;
            throw;
        }
    }
    
    T& get() {
        if (!valid) {
            throw std::runtime_error("Wrapper is invalid");
        }
        return value;
    }
    
    bool isValid() const {
        return valid;
    }
};

异常处理最佳实践

何时使用异常

适合使用异常:

// ✅ 构造函数失败(无法返回错误码)
class File {
public:
    File(const char* name) {
        fp = fopen(name, "r");
        if (!fp) {
            throw std::runtime_error("Cannot open file");
        }
    }
private:
    FILE* fp;
};

// ✅ 不可恢复的错误
void processData(const Data& data) {
    if (!data.isValid()) {
        throw std::invalid_argument("Invalid data");
    }
    // ...
}

// ✅ 跨多层函数传播错误
void deepFunction() {
    if (error) throw MyException();
}
void middleFunction() { deepFunction(); }
void topFunction() {
    try { middleFunction(); }
    catch (const MyException& e) { /* 处理 */ }
}

不适合使用异常:

// ❌ 正常控制流
void findElement(const vector<int>& vec, int target) {
    for (int x : vec) {
        if (x == target) {
            throw FoundException();  // 错误!用return
        }
    }
}

// ❌ 预期的错误(用错误码或optional)
std::optional<int> parseInt(const string& s) {
    try {
        return std::stoi(s);
    } catch (...) {
        return std::nullopt;  // 解析失败是预期的
    }
}

// ❌ 性能关键路径
void hotPath(int x) {
    if (x < 0) throw Ex();  // 异常有开销
}

性能考虑

异常的成本:

// 无异常路径:几乎零开销(现代编译器)
void normal() {
    // 正常执行,无性能损失
}

// 抛出异常:成本高
void throwing() {
    throw std::exception();  // 栈展开、对象析构、查找处理器
}

// 时间对比(近似):
// - 正常返回:1ns
// - 抛出并捕获异常:1000ns - 10000ns

优化建议:

// ✅ 1. noexcept用于不抛异常的函数
void fastFunction() noexcept {
    // 编译器可做更多优化
}

// ✅ 2. 移动操作标记noexcept(容器性能关键)
class MyClass {
public:
    MyClass(MyClass&& other) noexcept {
        // vector等容器在重新分配时会检查noexcept
        // 如果没有,会使用拷贝而非移动
    }
};

// ✅ 3. 异常只用于异常情况
// 正常错误(如文件不存在)用错误码或optional

// ✅ 4. catch引用避免切片
try {
    // ...
} catch (const std::exception& e) {  // 引用,保留派生类信息
    // ...
}

异常 vs 错误码

特性

异常

错误码

可读性

高(分离错误处理)

低(混杂正常逻辑)

强制处理

否(可能忘记catch)

是(必须检查返回值)

性能

正常路径快,异常路径慢

一致

构造函数

唯一选择

不可用

资源管理

自动(栈展开)

需手动清理

跨层传播

容易

繁琐

选择建议:

  • 库接口:异常(用户可选择处理方式)

  • 底层/实时系统:错误码(性能可预测)

  • 构造函数:异常(无法返回错误码)

  • C接口:错误码(C不支持异常)

  • 现代C++:优先异常 + RAII