「C/C++」C/C++异常篇 之 何时抛出异常,何时避免异常

在这里插入图片描述

✨博客主页
何曾参静谧的博客(✅关注、👍点赞、⭐收藏、🎠转发)
📚全部专栏(专栏会有变化,以最新发布为准)
「Win」Windows程序设计「IDE」集成开发环境「定制」定制开发集合
「C/C++」C/C++程序设计「DSA」数据结构与算法「UG/NX」NX二次开发
「QT」QT5程序设计「File」数据文件格式「UG/NX」BlockUI集合
「Py」Python程序设计「Math」探秘数学世界「PK」Parasolid函数说明
「Web」前后端全栈开发「En」英语从零到一👍占位符
「AI」人工智能大模型「书」书籍阅读笔记

C++异常处理详解:何时抛出异常,何时避免异常

1. 引言

在 C++ 中,异常(Exception)是一种错误处理机制,允许程序在运行时检测到错误并向上传递,直到被合适的 catch 块捕获。异常机制可以提高代码的可读性和健壮性,但滥用异常也可能导致性能问题和代码混乱。本文将详细讨论:

  • 何时应该抛出异常?
  • 何时应该避免异常?
  • C++ 异常的最佳实践

2. 何时应该抛出异常?

异常适用于不可恢复的错误违反程序逻辑的情况,调用方必须处理或终止程序。

2.1 不可恢复的错误

当函数无法完成其预期任务,且无法在内部处理时,应抛出异常:

double divide(double a, double b) {
    if (b == 0) {
        throw std::invalid_argument("Division by zero!");
    }
    return a / b;
}
  • 适用场景
    • 文件/网络访问失败(std::ifstream 打开文件失败)
    • 内存分配失败(new 抛出 std::bad_alloc
    • 无效输入参数(如 nullptr 检查)

2.2 违反前置条件(Preconditions)

如果函数要求某些条件必须满足,否则无法正确执行,应抛出异常:

void connectToDatabase(const std::string& url) {
    if (url.empty()) {
        throw std::invalid_argument("Database URL cannot be empty!");
    }
    // 连接逻辑...
}

2.3 构造函数失败

C++ 构造函数没有返回值,如果初始化失败,应抛出异常:

class FileHandler {
public:
    FileHandler(const std::string& filename) {
        file_.open(filename);
        if (!file_.is_open()) {
            throw std::runtime_error("Failed to open file: " + filename);
        }
    }
private:
    std::ifstream file_;
};

2.4 需要明确错误类型时

C++ 允许自定义异常类型,适用于需要区分不同错误的场景:

class NetworkError : public std::runtime_error {
public:
    NetworkError(const std::string& msg) : std::runtime_error(msg) {}
};

void sendData(const std::string& data) {
    if (!isNetworkAvailable()) {
        throw NetworkError("Network unavailable!");
    }
    // 发送数据...
}

3. 何时应该避免抛出异常?

异常机制有一定的性能开销(栈展开、跳转),在以下情况下应避免使用异常:

3.1 可预测且可恢复的错误

如果错误是预期的,并且可以立即处理,应使用返回值或 std::optional

std::optional<int> safeDivide(int a, int b) {
    if (b == 0) {
        return std::nullopt; // 不抛异常,返回空值
    }
    return a / b;
}

// 调用方处理
if (auto result = safeDivide(10, 0)) {
    std::cout << "Result: " << *result << "\n";
} else {
    std::cout << "Division failed!\n";
}

3.2 高频调用的代码路径

异常处理比普通返回慢,在性能关键代码(如游戏、高频交易)中应避免:

// 避免:
try {
    value = computeValue();
} catch (...) {
    value = defaultValue;
}

// 更优:
value = computeValueOrFallback(); // 内部处理错误,不抛异常

3.3 析构函数(Destructors)

C++ 析构函数默认 noexcept,如果抛出异常且未被捕获,程序会直接终止(std::terminate):

class ResourceHolder {
public:
    ~ResourceHolder() noexcept { // 确保不抛异常
        try {
            cleanup();
        } catch (...) {
            // 记录日志,但不要传播异常
        }
    }
};

3.4 跨语言/系统边界

如果代码需要与 C 或其他语言交互,应避免异常(C 无异常机制):

extern "C" void processData() noexcept { // 明确标记 noexcept
    try {
        // C++ 逻辑...
    } catch (...) {
        // 转换为错误码
    }
}

4. C++ 异常最佳实践

4.1 使用标准异常类型

优先使用 std 标准异常:

  • std::runtime_error(运行时错误)
  • std::invalid_argument(无效参数)
  • std::out_of_range(越界访问)
  • std::logic_error(逻辑错误)

4.2 避免抛出基本类型

❌ 错误:

throw "Something went wrong!"; // 不要抛出字符串

✅ 正确:

throw std::runtime_error("Something went wrong!");

4.3 使用 noexcept 标记不抛异常的函数

void safeOperation() noexcept { // 承诺不抛异常
    // 逻辑...
}

4.4 确保异常安全(Exception Safety)

  • 基本保证:即使抛出异常,对象仍处于有效状态。
  • 强保证:操作要么成功,要么回滚(如 std::vector::push_back)。
  • 不抛保证(noexcept:确保函数绝不抛异常。

5. 总结

场景是否抛出异常替代方案
不可恢复错误(如文件打不开)✅ 抛出-
违反前置条件(如 nullptr✅ 抛出assert(调试阶段)
构造函数失败✅ 抛出工厂模式(返回 std::optional
高频调用路径❌ 避免返回错误码 / std::expected
析构函数❌ 避免noexcept + 内部捕获
跨语言交互❌ 避免错误码(如 HRESULT

核心原则

  1. 异常用于不可恢复的错误,让调用方决定如何处理。
  2. 可预测的错误用返回值,避免异常影响性能。
  3. 确保异常安全,避免资源泄漏。
  4. 避免滥用异常,保持代码清晰高效。

通过合理使用异常,可以编写更健壮、更易维护的 C++ 代码!

何曾参静谧的博客(✅关注、👍点赞、⭐收藏、🎠转发)


在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何曾参静谧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值