1. 只在真正"异常"的情况下使用异常

异常应该用于处理意外错误(如文件不存在、网络连接失败),而不是用于常规控制流。

代码语言:javascript

AI代码解释

// 不推荐:使用异常进行常规控制流
try {
    int value = findValueInArray(arr, size);
} catch (NotFoundException& e) {
    // 这应该用简单的条件判断处理
}

// 推荐:异常用于意外错误
try {
    file.open("data.txt");
    if (!file.is_open()) {
        throw FileOpenException("无法打开文件"); // 这是真正的异常情况
    }
} catch (FileOpenException& e) {
    // 处理错误
}
2. 按引用捕获异常

始终通过const引用捕获异常,避免不必要的对象复制,同时支持多态捕获派生类异常:

代码语言:javascript

AI代码解释

// 推荐
try {
    // 可能抛出异常的代码
} catch (const std::out_of_range& e) {  // const引用
    // 处理特定异常
} catch (const std::exception& e) {     // 可以捕获所有std::exception派生类
    // 处理一般异常
}

// 不推荐:会导致对象切片和额外复制
catch (std::exception e) { ... }
3. 不要过度使用catch(...)

​catch(...)​​可以捕获所有类型的异常,但会隐藏错误细节,难以调试:

代码语言:javascript

AI代码解释

try {
    // 代码
} catch (...) {
    // 只在需要记录错误并重新抛出时使用
    logError("发生未知错误");
    throw; // 重新抛出,让上层处理
}
4. 使用RAII管理资源

异常可能导致资源泄漏,使用RAII(资源获取即初始化)技术确保资源自动释放:

代码语言:javascript

AI代码解释

// 推荐:使用智能指针自动管理内存
void process() {
    std::unique_ptr<Resource> res(new Resource());
    // 如果这里抛出异常,unique_ptr会自动释放资源
}

// 不推荐:手动管理资源容易泄漏
void process() {
    Resource* res = new Resource();
    // 如果发生异常,delete不会执行
    delete res;
}
5. 定义有意义的异常类型

创建特定的异常类,提供详细的错误信息,便于精确处理:

代码语言:javascript

AI代码解释

// 自定义异常层次结构
class NetworkException : public std::exception {
public:
    explicit NetworkException(const std::string& msg) : msg_(msg) {}
    const char* what() const noexcept override { return msg_.c_str(); }
private:
    std::string msg_;
};

class ConnectionException : public NetworkException {
    using NetworkException::NetworkException; // 继承构造函数
};

// 使用时可以精确捕获
try {
    connectToServer();
} catch (const ConnectionException& e) {
    // 处理连接异常
} catch (const NetworkException& e) {
    // 处理其他网络异常
}
6. 正确使用noexcept

使用​​noexcept​​明确标记不会抛出异常的函数,帮助编译器优化并提高可读性:

代码语言:javascript

AI代码解释

// 明确不会抛出异常的函数
void swap(Data& a, Data& b) noexcept {
    // 简单操作,不会抛出异常
}

// 可能抛出异常的函数(默认情况)
void parseInput(const std::string& input) {
    if (input.empty()) {
        throw std::invalid_argument("输入为空");
    }
}
7. 保持异常安全

设计函数时考虑三种异常安全级别:

  • 基本保证:异常发生后,程序处于有效状态,但不一定保持原始状态
  • 强保证:异常发生后,程序状态完全恢复到调用前的状态
  • 不抛出保证:函数绝对不会抛出异常(使用noexcept)

代码语言:javascript

AI代码解释

// 强保证示例:使用copy-and-swap
void updateData(Data& data, const NewValue& value) {
    Data temp = data; // 复制当前状态
    temp.modify(value); // 可能抛出异常
    std::swap(data, temp); // 不抛出异常
}
8. 避免在析构函数中抛出异常

析构函数抛出异常可能导致程序终止或未定义行为:

代码语言:javascript

AI代码解释

// 不推荐
~Resource() {
    if (close() != 0) {
        throw CloseException("关闭失败"); // 危险!
    }
}

// 推荐
~Resource() noexcept {
    if (close() != 0) {
        logError("关闭失败"); // 记录错误但不抛出
    }
}
9. 异常与错误码的选择
  • 当错误需要被上层处理时,使用异常
  • 当错误可以在本地处理,且是预期的常见情况时,考虑使用错误码
10. 文档化异常

明确记录函数可能抛出的异常类型,帮助使用者正确处理:

代码语言:javascript

AI代码解释

/**
 * 打开指定文件
 * @param filename 文件名
 * @throw FileOpenException 如果文件无法打开
 * @throw PermissionDeniedException 如果没有权限
 */
void openFile(const std::string& filename);

遵循这些实践可以帮助你编写更可靠、更易维护的C++代码,充分发挥异常处理的优势,同时避免常见陷阱。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐