【C++进阶】异常

【C++进阶】异常

1. 传统错误处理方式

在C++之前,C语言主要使用两种错误处理方式:

1.1 终止程序

cpp 复制代码
#include <cassert>

void traditional_approach1() {
    int* ptr = malloc(sizeof(int) * 100);
    assert(ptr != nullptr);  // 如果分配失败,直接终止程序
    
    // 使用内存...
    free(ptr);
}

缺点:用户体验差,程序突然崩溃。

1.2 返回错误码

cpp 复制代码
#include <cerrno>
#include <cstring>

int traditional_approach2() {
    FILE* file = fopen("nonexistent.txt", "r");
    if (file == nullptr) {
        printf("错误: %s\n", strerror(errno));  // 返回错误码
        return -1;
    }
    
    // 处理文件...
    fclose(file);
    return 0;
}

缺点:需要程序员手动检查错误码,容易遗漏。

2. C++异常概念

C++引入异常机制,提供更优雅的错误处理方式:

  • throw:抛出异常
  • try:尝试执行可能抛出异常的代码
  • catch:捕获并处理异常
cpp 复制代码
#include <iostream>
#include <stdexcept>

double divide(int a, int b) {
    if (b == 0) {
        throw std::runtime_error("除数不能为零!");
    }
    return static_cast<double>(a) / b;
}

int main() {
    try {
        double result = divide(10, 0);
        std::cout << "结果: " << result << std::endl;
    }
    catch (const std::exception& e) {
        std::cout << "捕获异常: " << e.what() << std::endl;
    }
    
    return 0;
}

3. 异常的使用

3.1 异常的抛出和捕获

异常匹配原则

  1. 异常通过抛出对象引发,对象类型决定匹配的catch块
  2. 选择调用链中类型匹配且距离最近的catch块
  3. 抛出异常会生成对象拷贝
  4. catch(...) 可捕获任意类型异常
  5. 可以用基类捕获派生类异常(多态)
cpp 复制代码
#include <iostream>
#include <string>

void func3() {
    throw std::string("在func3中发生错误!");
}

void func2() {
    func3();
}

void func1() {
    func2();
}

int main() {
    try {
        func1();
    }
    catch (const std::string& msg) {
        std::cout << "字符串异常: " << msg << std::endl;
    }
    catch (const char* msg) {
        std::cout << "C字符串异常: " << msg << std::endl;
    }
    catch (...) {
        std::cout << "未知异常" << std::endl;
    }
    
    return 0;
}

3.2 异常的重新抛出

cpp 复制代码
#include <iostream>
#include <memory>

void processResource() {
    auto resource = std::make_unique<int>(42);
    
    try {
        // 模拟可能抛出异常的操作
        throw std::runtime_error("处理资源时发生错误");
    }
    catch (...) {
        std::cout << "清理资源并重新抛出异常" << std::endl;
        // 资源会被unique_ptr自动释放
        throw;  // 重新抛出
    }
}

int main() {
    try {
        processResource();
    }
    catch (const std::exception& e) {
        std::cout << "主函数捕获: " << e.what() << std::endl;
    }
    
    return 0;
}

3.3 异常安全

重要准则

  • 构造函数:避免抛出异常,可能导致对象不完整
  • 析构函数:绝对不要抛出异常,可能导致资源泄漏
  • 使用RAII(资源获取即初始化)管理资源
cpp 复制代码
#include <memory>
#include <vector>

class DatabaseConnection {
private:
    std::unique_ptr<int> connection;
    
public:
    DatabaseConnection() {
        connection = std::make_unique<int>(1);
        // 如果这里抛出异常,对象构造失败是安全的
    }
    
    ~DatabaseConnection() {
        // 析构函数不抛出异常
        // 资源由unique_ptr自动管理
    }
    
    // 禁用拷贝,避免资源管理问题
    DatabaseConnection(const DatabaseConnection&) = delete;
    DatabaseConnection& operator=(const DatabaseConnection&) = delete;
};

3.4 异常规范

cpp 复制代码
#include <iostream>

// C++98风格:可能抛出int或double异常
void old_style() throw(int, double) {
    throw 42;
}

// C++11风格:不抛出异常
void modern_style() noexcept {
    // 这个函数保证不会抛出异常
}

// 无异常规范:可能抛出任何异常
void any_exception() {
    throw std::runtime_error("任何异常");
}

int main() {
    try {
        old_style();
    }
    catch (int e) {
        std::cout << "捕获int异常: " << e << std::endl;
    }
    catch (double e) {
        std::cout << "捕获double异常: " << e << std::endl;
    }
    
    return 0;
}

4. 自定义异常体系

实际项目中通常需要自定义异常体系:

cpp 复制代码
#include <iostream>
#include <string>
#include <memory>

// 基础异常类
class Exception {
protected:
    std::string _errmsg;
    int _id;
    
public:
    Exception(const std::string& errmsg, int id)
        : _errmsg(errmsg), _id(id) {}
    
    virtual ~Exception() = default;
    
    virtual std::string what() const {
        return _errmsg + " (错误码: " + std::to_string(_id) + ")";
    }
};

// 数据库异常
class SqlException : public Exception {
private:
    std::string _sql;
    
public:
    SqlException(const std::string& errmsg, int id, const std::string& sql)
        : Exception(errmsg, id), _sql(sql) {}
    
    std::string what() const override {
        return "SQL异常: " + _errmsg + " -> " + _sql;
    }
};

// 缓存异常
class CacheException : public Exception {
public:
    CacheException(const std::string& errmsg, int id)
        : Exception(errmsg, id) {}
    
    std::string what() const override {
        return "缓存异常: " + _errmsg;
    }
};

// HTTP服务器异常
class HttpServerException : public Exception {
private:
    std::string _type;
    
public:
    HttpServerException(const std::string& errmsg, int id, const std::string& type)
        : Exception(errmsg, id), _type(type) {}
    
    std::string what() const override {
        return "HTTP服务器异常[" + _type + "]: " + _errmsg;
    }
};

// 模拟业务逻辑
void processDatabase() {
    // 模拟数据库操作可能抛出异常
    throw SqlException("权限不足", 1001, "SELECT * FROM users");
}

void processCache() {
    // 模拟缓存操作
    throw CacheException("缓存键不存在", 2001);
}

void handleHttpRequest() {
    // 模拟HTTP请求处理
    throw HttpServerException("请求超时", 3001, "GET");
}

int main() {
    try {
        handleHttpRequest();
        processCache();
        processDatabase();
    }
    catch (const Exception& e) {
        // 多态处理:一个catch块处理所有派生异常
        std::cout << e.what() << std::endl;
    }
    catch (...) {
        std::cout << "未知异常" << std::endl;
    }
    
    return 0;
}

5. C++标准库异常体系

C++标准库提供了一套完整的异常体系:

cpp 复制代码
#include <iostream>
#include <stdexcept>
#include <vector>
#include <memory>

void demonstrate_standard_exceptions() {
    try {
        // 1. bad_alloc - 内存分配失败
        std::unique_ptr<int[]> huge_array = 
            std::make_unique<int[]>(1000000000000LL);
    }
    catch (const std::bad_alloc& e) {
        std::cout << "内存分配失败: " << e.what() << std::endl;
    }
    
    try {
        // 2. out_of_range - 下标越界
        std::vector<int> vec = {1, 2, 3};
        int value = vec.at(10);  // 抛出std::out_of_range
    }
    catch (const std::out_of_range& e) {
        std::cout << "下标越界: " << e.what() << std::endl;
    }
    
    try {
        // 3. invalid_argument - 无效参数
        std::string str("hello");
        int num = std::stoi(str);  // 可能抛出std::invalid_argument
    }
    catch (const std::invalid_argument& e) {
        std::cout << "无效参数: " << e.what() << std::endl;
    }
    
    try {
        // 4. logic_error - 逻辑错误
        throw std::logic_error("逻辑错误示例");
    }
    catch (const std::logic_error& e) {
        std::cout << "逻辑错误: " << e.what() << std::endl;
    }
}

int main() {
    demonstrate_standard_exceptions();
    return 0;
}

标准异常类层次结构

复制代码
std::exception
├── std::bad_alloc
├── std::bad_cast
├── std::bad_typeid
├── std::bad_exception
├── std::logic_error
│   ├── std::domain_error
│   ├── std::invalid_argument
│   ├── std::length_error
│   └── std::out_of_range
└── std::runtime_error
    ├── std::overflow_error
    ├── std::range_error
    ├── std::underflow_error
    └── std::system_error

6. 异常的优缺点

6.1 优点

1. 清晰的错误信息

cpp 复制代码
// 传统方式
int connectDatabase() {
    if (/* 连接失败 */) return -1;
    if (/* 权限不足 */) return -2;
    return 0;
}

// 异常方式
void connectDatabaseWithException() {
    if (/* 连接失败 */) 
        throw std::runtime_error("数据库连接失败: 网络超时");
    if (/* 权限不足 */)
        throw std::runtime_error("数据库连接失败: 权限不足");
}

2. 调用链简化

cpp 复制代码
void deepFunction() {
    throw std::runtime_error("深层错误");
}

void middleFunction() {
    deepFunction();  // 不需要检查返回值
}

void topFunction() {
    try {
        middleFunction();
    }
    catch (const std::exception& e) {
        std::cout << "统一处理: " << e.what() << std::endl;
    }
}

3. 构造函数错误处理

cpp 复制代码
class FileHandler {
private:
    std::FILE* file;
    
public:
    FileHandler(const std::string& filename) {
        file = std::fopen(filename.c_str(), "r");
        if (!file) {
            throw std::runtime_error("无法打开文件: " + filename);
        }
    }
    
    ~FileHandler() {
        if (file) std::fclose(file);
    }
};

6.2 缺点

1. 性能开销 :异常处理有一定性能代价
2. 代码复杂度 :可能使控制流难以跟踪
3. 资源管理:需要RAII确保异常安全

7. 推荐用法

7.1 RAII确保异常安全

cpp 复制代码
#include <memory>
#include <fstream>

class SafeFile {
private:
    std::unique_ptr<std::FILE, decltype(&std::fclose)> file;
    
public:
    SafeFile(const std::string& filename, const std::string& mode)
        : file(std::fopen(filename.c_str(), mode.c_str()), &std::fclose) {
        
        if (!file) {
            throw std::runtime_error("无法打开文件: " + filename);
        }
    }
    
    void write(const std::string& content) {
        if (std::fputs(content.c_str(), file.get()) == EOF) {
            throw std::runtime_error("写入文件失败");
        }
    }
};

void safeFileOperation() {
    SafeFile file("data.txt", "w");
    file.write("安全的数据写入");
    // 即使抛出异常,文件也会自动关闭
}

7.2 异常规范建议

cpp 复制代码
class RobustClass {
public:
    // 明确标注不抛异常的函数
    void simpleOperation() noexcept {
        // 简单操作,保证不抛异常
    }
    
    // 可能抛异常的函数要明确文档说明
    /**
     * @brief 复杂操作,可能抛出std::runtime_error
     * @throws std::runtime_error 当操作失败时
     */
    void complexOperation() {
        if (/* 失败条件 */) {
            throw std::runtime_error("操作失败");
        }
    }
    
    // 析构函数绝对不抛异常
    ~RobustClass() noexcept {
        // 清理资源,确保不抛异常
    }
};

总结

C++异常处理提供了强大的错误处理机制:

  • 优势:清晰的错误信息、简化的错误传播、构造函数错误处理
  • 挑战:性能开销、代码复杂度、资源管理
  • 推荐用法:使用RAII、自定义异常体系、明确的异常规范
相关推荐
百锦再2 小时前
大话Rust的前生今世
开发语言·后端·rust·go·内存·时间·抽象
QT 小鲜肉2 小时前
【C++基础与提高】第十一章:面向对象编程进阶——继承与多态
java·linux·开发语言·c++·笔记·qt
艾莉丝努力练剑2 小时前
【C++:封装红黑树】C++红黑树封装实战:从零实现MyMap与MySet
c++·stl·set·map·红黑树·平衡二叉树
序属秋秋秋3 小时前
《Linux系统编程之进程基础》【进程入门】
linux·运维·c语言·c++·进程·系统编程·fork
点云SLAM3 小时前
Boost库中Boost.PropertyTree使用和实战示例
开发语言·c++·josn·boost库·参数读取
晨非辰3 小时前
【数据结构】排序详解:从快速排序分区逻辑,到携手冒泡排序的算法效率深度评测
运维·数据结构·c++·人工智能·后端·深度学习·排序算法
lly2024063 小时前
CSS3 分页技术解析
开发语言
CodeCraft Studio3 小时前
国产化Excel开发组件Spire.XLS教程:Python将列表导出为CSV文件(含一维/二维/字典列表)
开发语言·python·excel·csv·spire.xls·列表导出为csv