c++ 异常处理1


1. 传统错误处理 vs C++异常处理

传统方法:返回值表示错误
int divide(int a, int b, int& result) {
    if (b == 0) return -1; // 错误码
    result = a / b;
    return 0; // 成功
}

int main() {
    int res;
    if (divide(10, 0, res) != 0) {
        std::cerr << "Division by zero!" << std::endl;
    }
}
  • 问题:错误码可能被忽略,且返回值被占用(无法同时返回结果和状态)。
  • 优点:简单直观,但难以处理多层嵌套调用的错误。
C++异常处理:try/catch/throw
int divide(int a, int b) {
    if (b == 0) throw std::runtime_error("Division by zero!");
    return a / b;
}

int main() {
    try {
        int res = divide(10, 0);
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
}
  • 优势:错误处理与正常逻辑分离,支持跨多层函数调用传递错误。

2. 异常触发与栈展开

栈展开(Stack Unwinding)

当异常抛出时:

  1. 当前函数停止执行,局部对象按构造逆序析构。
  2. 系统沿调用栈向上查找匹配的catch块。
  3. 若未找到,程序终止(调用std::terminate)。
struct Logger {
    Logger() { std::cout << "Logger created\n"; }
    ~Logger() { std::cout << "Logger destroyed\n"; }
};

void funcB() {
    Logger log;
    throw std::runtime_error("Error in funcB");
}

void funcA() {
    Logger log;
    funcB();
}

int main() {
    try {
        funcA();
    } catch (...) {
        std::cout << "Exception caught\n";
    }
}

输出

Logger created (funcA)
Logger created (funcB)
Logger destroyed (funcB) <- 栈展开开始
Logger destroyed (funcA)
Exception caught

3. 异常对象

  • 抛出异常时,系统用异常表达式拷贝初始化一个临时对象(称为异常对象)。
  • 异常对象在栈展开期间存活,直到catch块结束。
示例:引用捕获避免拷贝
try {
    throw std::runtime_error("Error");
} catch (const std::exception& e) { // 引用捕获,避免拷贝
    std::cerr << e.what();
}

4. try/catch语句块

基本语法与匹配顺序
try {
    // 可能抛出异常的代码
} catch (const std::runtime_error& e) {
    // 处理特定异常
} catch (const std::exception& e) {
    // 处理基类异常
} catch (...) {
    // 处理所有其他异常
}
  • 匹配顺序:从上到下,第一个匹配的catch块被执行。
  • catch(...):捕获任意异常,通常用于最后兜底。
重新抛出异常
try {
    throw std::runtime_error("Original error");
} catch (...) {
    std::cout << "Logging error...\n";
    throw; // 重新抛出原异常
}

5. 异常与构造/析构函数

构造函数中的异常
  • 若构造函数抛出异常:
    • 已构造的成员和基类子对象会被析构。
    • 当前对象的析构函数不会被调用。
class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource released\n"; }
};

class Widget {
    Resource res;
public:
    Widget() {
        throw std::runtime_error("Widget failed");
    }
    ~Widget() { /* 不会执行 */ }
};

int main() {
    try {
        Widget w;
    } catch (...) {
        std::cout << "Exception caught\n";
    }
}

输出

Resource acquired
Resource released  // 成员res被析构
Exception caught
析构函数中的异常
  • 危险行为:析构函数中抛出异常可能导致程序终止。
class Danger {
public:
    ~Danger() noexcept(false) {
        throw std::runtime_error("Oops");
    }
};

int main() {
    try {
        Danger d;
    } catch (...) {
        // 析构函数抛出的异常可能未被捕获
    }
} // 若异常未被捕获,调用std::terminate

6. noexcept关键字

声明函数不抛异常
void safeFunc() noexcept {
    // 保证不抛异常
}

void riskyFunc() noexcept(false) {
    // 可能抛异常
}
noexcept操作符
void funcA() noexcept {}
void funcB() { throw 42; }

static_assert(noexcept(funcA())); //noexcept(funcA())  true
static_assert(!noexcept(funcB())); //noexcept(funcB()) false
noexcept与性能
  • 标记noexcept的函数允许编译器优化(如省略栈展开代码)。
  • 违反noexcept声明会导致std::terminate
void terminateIfThrows() noexcept {
    throw 42; // 程序终止
}

总结与最佳实践

  1. 优先使用异常处理错误,而非返回值。
  2. 析构函数禁止抛异常(标记为noexcept)。
  3. 按引用捕获异常(避免对象切片和拷贝)。
  4. 合理使用noexcept,优化性能并明确接口契约。
  5. 构造函数失败时抛异常,避免返回半构造对象。

通过结合RAII(资源获取即初始化)模式,可确保异常安全:

class FileHandler {
    FILE* file;
public:
    FileHandler(const char* path) : file(fopen(path, "r")) {
        if (!file) throw std::runtime_error("File open failed");
    }
    ~FileHandler() { if (file) fclose(file); }
};
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值