C++异常处理 终极及总结

1.1 noexcept 的两种形式

cpp

复制代码
// 形式1:简单声明(不会抛异常)
void func1() noexcept;          // 等同于 noexcept(true)
void func2() noexcept();        // 同上
void func3() noexcept(true);    // 明确声明不抛异常

// 形式2:条件性声明
void func4() noexcept(false);   // 可能抛异常
void func5() noexcept(表达式);   // 根据表达式结果决定
1.2 noexcept 作为操作符

这是模板编程中非常重要的特性:

cpp

复制代码
#include <iostream>
#include <type_traits>
using namespace std;

class SafeType {
public:
    SafeType() noexcept {}  // 不抛异常的构造函数
};

class RiskyType {
public:
    RiskyType() { throw 1; }  // 可能抛异常的构造函数
};

// noexcept操作符:检查表达式是否会抛异常
template<class T>
void templateFunc() noexcept(noexcept(T())) {
    T obj;  // 根据T()是否noexcept,整个函数也是noexcept的
}

void testNoexceptOperator() {
    cout << "SafeType构造是否noexcept: " << noexcept(SafeType()) << endl;    // 输出1
    cout << "RiskyType构造是否noexcept: " << noexcept(RiskyType()) << endl;  // 输出0
    
    // 在模板中应用
    cout << "templateFunc<SafeType>是否noexcept: " 
         << noexcept(templateFunc<SafeType>()) << endl;  // 输出1
}
1.3 实际应用场景(非常重要!)

移动操作必须声明为 noexcept

cpp

复制代码
class Vector {
private:
    int* data;
    size_t size;
    
public:
    // 移动构造函数 - 必须noexcept!
    Vector(Vector&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;  // 偷取资源
        other.size = 0;
    }
    
    // 移动赋值运算符 - 必须noexcept!
    Vector& operator=(Vector&& other) noexcept {
        if (this != &other) {
            delete[] data;      // 释放现有资源
            data = other.data;  // 偷取资源
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }
    
    // 析构函数 - 默认就是noexcept
    ~Vector() {
        delete[] data;
    }
};

为什么移动操作要 noexcept

  • std::vector 等容器在重新分配内存时,如果移动构造函数不是 noexcept,会 fallback 到拷贝构造

  • 这会导致性能损失

二、C++异常机制使用调查

这是工程实践中的宝贵经验,很多是实际项目中的血泪教训。

2.1 禁止使用异常的场景和原因

性能开销:

cpp

复制代码
// 异常机制的开销来源:
// 1. 异常表(exception table)的维护
// 2. 栈展开时的析构函数调用
// 3. 运行时类型信息(RTTI)查询
// 4. 破坏了编译器的优化机会

// 在性能敏感的场景(如高频交易、嵌入式系统)中避免使用

资源泄漏风险:

cpp

复制代码
void riskyFunction() {
    FILE* file = fopen("data.txt", "r");
    int* buffer = new int[1000];
    
    someOperationThatMightThrow();  // 如果这里抛出异常...
    
    fclose(file);    // 这行不会执行!
    delete[] buffer; // 这行也不会执行!
}

// 即使使用try-catch,也要小心:
void stillRisky() {
    FILE* file = fopen("data.txt", "r");
    try {
        int* buffer = new int[1000];
        someOperationThatMightThrow();
        delete[] buffer;
    } catch (...) {
        fclose(file);  // 记得关闭文件
        throw;         // 重新抛出
    }
    fclose(file);      // 正常路径也要关闭
}
2.2 安全编码建议

错误码方式的优势:

cpp

复制代码
enum class ErrorCode {
    Success = 0,
    InvalidArgument,
    OutOfMemory,
    FileNotFound
};

class Result {
private:
    ErrorCode code;
    std::string message;
    
public:
    Result(ErrorCode c = ErrorCode::Success, const std::string& msg = "")
        : code(c), message(msg) {}
    
    bool isSuccess() const { return code == ErrorCode::Success; }
    ErrorCode getCode() const { return code; }
    const std::string& getMessage() const { return message; }
};

Result processData(const std::string& input) {
    if (input.empty()) {
        return Result(ErrorCode::InvalidArgument, "输入不能为空");
    }
    
    // 处理逻辑...
    return Result(ErrorCode::Success);
}

三、标准库异常类的完整体系

3.1 完整的异常类层次
复制代码
std::exception
├── std::logic_error
│   ├── std::length_error      // 长度错误(如vector::resize)
│   ├── std::domain_error      // 定义域错误(数学函数)
│   ├── std::out_of_range      // 越界访问
│   └── std::invalid_argument  // 无效参数
├── std::runtime_error
│   ├── std::range_error       // 结果超出值域
│   ├── std::overflow_error    // 算术上溢
│   └── std::underflow_error   // 算术下溢
├── std::bad_alloc             // new失败
├── std::bad_cast              // dynamic_cast失败
├── std::bad_typeid           // typeid(nullptr)
└── std::bad_exception        // 意外异常
3.2 具体使用示例

cpp

复制代码
#include <stdexcept>
#include <vector>
#include <string>
#include <iostream>

void demonstrateStandardExceptions() {
    try {
        // 1. length_error
        std::vector<int> vec;
        vec.resize(vec.max_size() + 1);  // 抛出 std::length_error
        
    } catch (const std::length_error& e) {
        std::cout << "长度错误: " << e.what() << std::endl;
    }
    
    try {
        // 2. out_of_range
        std::string str = "hello";
        char c = str.at(10);  // 抛出 std::out_of_range
        
    } catch (const std::out_of_range& e) {
        std::cout << "越界访问: " << e.what() << std::endl;
    }
    
    try {
        // 3. invalid_argument
        std::bitset<8> b("01234567");  // 非0/1字符,抛出 std::invalid_argument
        
    } catch (const std::invalid_argument& e) {
        std::cout << "无效参数: " << e.what() << std::endl;
    }
}

四、异常规范的历史演变

4.1 C++98 的动态异常规范(已废弃)

cpp

复制代码
// 旧的语法(C++17中已移除)
void oldFunction() throw(int, std::string);  // 只能抛出这些类型
void noThrowOld() throw();                   // 不抛出任何异常

// 问题:只在运行时检查,性能开销大,实际使用很少
4.2 C++11 的 noexcept(现代替代)
复制代码
// 现代语法
void modernFunction() noexcept;              // 不抛出任何异常
void mayThrowFunction() noexcept(false);     // 可能抛出异常
void conditionalNoexcept() noexcept(noexcept(expression));

// 优势:编译期决定,性能更好

五、工程实践建议总结

根据PDF中的"异常机制使用调查",在实际项目中:

5.1 推荐使用异常的场景:
  • 接收第三方库抛出的异常

  • 处理标准库抛出的异常(如 new 失败)

  • 在应用程序的顶层进行统一的错误处理

5.2 避免使用异常的场景:
  • 性能敏感的代码路径

  • 嵌入式系统或资源受限环境

  • 与C语言的接口边界

  • 构造函数中的复杂初始化(考虑使用工厂函数)

5.3 现代C++的最佳实践:

cpp

复制代码
class ModernClass {
public:
    // 1. 默认让析构函数为 noexcept(编译器默认这样做)
    ~ModernClass() = default;
    
    // 2. 移动操作为 noexcept
    ModernClass(ModernClass&&) noexcept = default;
    ModernClass& operator=(ModernClass&&) noexcept = default;
    
    // 3. 简单操作标记 noexcept
    int getValue() const noexcept { return value; }
    
    // 4. 可能失败的操作返回 Result 或 optional
    std::optional<int> tryGetValue() const {
        if (isValid()) return value;
        return std::nullopt;
    }
    
private:
    int value;
};
相关推荐
Algo-hx42 分钟前
C++编程基础(九):预处理指令
c++
tobebetter95276 小时前
How to manage python versions on windows
开发语言·windows·python
9***P3347 小时前
PHP代码覆盖率
开发语言·php·代码覆盖率
CoderYanger7 小时前
优选算法-栈:67.基本计算器Ⅱ
java·开发语言·算法·leetcode·职场和发展·1024程序员节
jllllyuz7 小时前
Matlab实现基于Matrix Pencil算法实现声源信号角度和时间估计
开发语言·算法·matlab
多多*8 小时前
Java复习 操作系统原理 计算机网络相关 2025年11月23日
java·开发语言·网络·算法·spring·microsoft·maven
凌康ACG8 小时前
Sciter之c++与前端交互(五)
c++·sciter
p***43488 小时前
Rust网络编程模型
开发语言·网络·rust
ᐇ9598 小时前
Java集合框架深度实战:构建智能教育管理与娱乐系统
java·开发语言·娱乐