1.c++ 异常处理
记忆要点
catch (...) { // 捕获所有未处理的异常
C++异常处理是一种用于处理运行时错误(如无效输入、资源不足等)的机制,通过try、catch和throw关键字实现。以下是核心要点和最佳实践:
1. 基本语法
- 抛出异常:使用
throw关键字抛出异常对象。cppvoid riskyFunction(int value) { if (value < 0) throw std::invalid_argument("Negative value not allowed"); } - 捕获异常:使用
try块包裹可能抛出异常的代码,通过catch块处理特定类型的异常。cpp1try { 2 riskyFunction(-1); 3} catch (const std::invalid_argument& e) { 4 std::cerr << "捕获到无效参数异常: " << e.what() << std::endl; 5} catch (...) { // 捕获所有未处理的异常 6 std::cerr << "捕获到未知异常" << std::endl; 7}
2. 标准异常类
C++标准库提供了一系列异常类(定义在<stdexcept>和<exception>中):
- 基类:
std::exception(包含what()方法返回错误信息)。 - 常用派生类:
std::logic_error:逻辑错误(如无效参数)。std::runtime_error:运行时错误(如文件打开失败)。std::bad_alloc:内存分配失败。std::out_of_range:越界访问。
3. 异常处理机制
- 栈展开(Stack Unwinding):抛出异常时,C++运行时系统会按调用栈反向回溯,销毁局部对象(调用析构函数),直到找到匹配的
catch块。 - 资源管理:结合RAII(资源获取即初始化)模式,确保异常发生时资源(如内存、文件句柄)被正确释放。例如使用智能指针
std::unique_ptr或std::shared_ptr。
4. 最佳实践
- 避免在析构函数中抛出异常:析构函数中抛出的异常若未被捕获,会导致
std::terminate终止程序。 - 优先使用标准异常类:自定义异常类应继承自
std::exception,并重写what()方法。cpp1class MyException : public std::runtime_error { 2public: 3 MyException(const std::string& msg) : std::runtime_error(msg) {} 4}; - 使用
noexcept关键字:声明函数不抛出异常(C++11起),可优化性能。cpp1void noThrowFunction() noexcept { /* ... */ } - 避免捕获所有异常(
catch(...)):除非用于资源清理(如释放锁),否则应明确捕获特定异常类型。 - 异常安全设计:
- 基本保证:对象保持有效状态,但资源可能泄漏。
- 强保证:操作要么完全成功,要么回滚到原始状态(如使用拷贝交换技术)。
- 无异常保证(
noexcept):函数保证不抛出异常。
5. 性能与场景
- 性能开销:异常处理机制在正常运行时不产生开销,但抛出和捕获异常的成本较高(涉及栈展开和对象销毁)。
- 适用场景:
- 适合处理不可恢复的错误(如无效用户输入、资源耗尽)。
- 在性能敏感的代码(如实时系统)中,优先考虑错误码或状态标志。
6. 常见问题
- 构造函数中的异常:若构造函数抛出异常,对象视为未完全构造,已分配的资源需通过RAII自动释放。
- 异常规范(已弃用):C++11前使用
throw(type)声明异常类型,现推荐使用noexcept。 - 避免滥用异常:异常不应替代常规控制流(如循环条件),否则会降低代码可读性。
示例代码
cpp
#include <iostream>
#include <stdexcept>
double divide(double a, double b) {
if (b == 0) throw std::runtime_error("Division by zero");
return a / b;
}
int main() {
try {
double result = divide(10.0, 0.0);
std::cout << "Result: " << result << std::endl;
} catch (const std::runtime_error& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
通过合理使用异常处理,可以提升代码的健壮性和可维护性,同时需权衡性能与可读性,选择最适合场景的错误处理方式。
2.c++ 对那些异常的处理可以避免系统崩溃
在C++中,通过合理使用异常处理机制可以有效避免系统崩溃。以下是关键实践和原则:
1. 基础异常处理结构
- try-catch块:用
try包裹可能抛出异常的代码,通过catch捕获并处理特定异常。cpp1try { 2 // 可能抛出异常的代码(如文件操作、内存分配) 3} catch (const std::exception& e) { 4 // 处理标准异常(如std::runtime_error, std::out_of_range) 5 std::cerr << "标准异常: " << e.what() << std::endl; 6} catch (const std::string& e) { 7 // 捕获自定义字符串异常 8} catch (...) { 9 // 捕获所有未预料的异常(谨慎使用) 10 std::cerr << "未知异常,执行清理操作" << std::endl; 11}
2. 避免系统崩溃的核心策略
-
资源管理(RAII模式):
- 使用智能指针(
std::unique_ptr,std::shared_ptr)自动管理内存,避免内存泄漏或悬空指针。 - 通过栈上对象管理资源(如文件句柄、锁),利用析构函数自动释放资源。
cpp1void processFile() { 2 std::ifstream file("data.txt"); 3 if (!file) throw std::runtime_error("文件打开失败"); 4 // 文件会在作用域结束时自动关闭 5} - 使用智能指针(
-
避免在析构函数中抛出异常:
- 析构函数抛异常会导致
std::terminate调用(程序终止)。确保析构函数安全(使用noexcept标记)。
cpp1class Resource { 2public: 3 ~Resource() noexcept { /* 清理代码,不抛异常 */ } 4}; - 析构函数抛异常会导致
-
验证输入和状态:
- 在操作前检查参数有效性,避免无效操作引发异常。
cpp1void safeDivide(int a, int b) { 2 if (b == 0) throw std::invalid_argument("除数不能为零"); 3 // 执行计算 4}
3. 异常安全保证
- 基本保证:操作后对象处于有效状态,资源不泄漏。
- 强保证:操作要么完全成功,要么回滚到原始状态(如事务性操作)。
- 无异常保证:函数保证不抛出异常(如
noexcept修饰符)。
4. 自定义异常
- 继承
std::exception创建有意义的异常类型,便于针对性处理。cpp1class MyError : public std::runtime_error { 2public: 3 MyError(const std::string& msg) : std::runtime_error(msg) {} 4};
5. 避免常见陷阱
- 不要忽略异常:捕获后必须处理(如记录日志、回滚操作),避免空
catch块。 - 避免过度使用异常:在性能敏感代码中优先用错误码(如系统调用、实时处理)。
- 线程安全:多线程中异常处理需确保线程同步(如共享资源访问)。
6. 特殊场景处理
- 内存不足:捕获
std::bad_alloc,尝试恢复或优雅降级。cpp1try { 2 int* ptr = new int[1000000]; 3} catch (const std::bad_alloc& e) { 4 std::cerr << "内存不足: " << e.what() << std::endl; 5 // 释放其他资源或终止操作 6} - 外部库异常:确保第三方库异常被正确捕获(如通过适配器包装)。
7. 最佳实践
- 优先使用标准异常:减少自定义类型,提高代码一致性。
- 使用
noexcept标记:明确函数是否抛异常,优化编译器优化。 - 日志记录:所有异常应记录上下文信息(如时间、操作、参数),便于调试。
- 单元测试:验证异常场景下的代码行为,确保符合预期。
通过以上策略,C++程序可以在异常发生时安全地处理错误,避免崩溃,同时保持代码的健壮性和可维护性。关键点在于资源自动管理、异常分层捕获和状态一致性保证。
3.C++异常处理:何时能避免程序崩溃?
在C++中,异常处理机制(try-catch)确实可以通过捕获和处理特定类型的异常来避免程序崩溃,但并非所有运行时错误都能通过异常处理解决。以下是系统性的分析:
一、可捕获并避免崩溃的异常
C++标准库和用户自定义的异常类型,只要通过throw抛出,均可被catch块捕获处理,从而避免程序终止:
1. 标准库异常
-
内存分配失败:
std::bad_alloc(如new操作失败)cpp1try { 2 int* arr = new int[1000000000]; // 可能抛出std::bad_alloc 3} catch (const std::bad_alloc& e) { 4 std::cerr << "内存不足: " << e.what(); 5 // 执行降级处理(如使用较小数据量重试) 6} -
类型转换错误:
std::bad_cast(dynamic_cast失败)、std::bad_typeid(typeid操作无效) -
逻辑错误:
std::logic_error(如std::invalid_argument、std::out_of_range)
cpp1std::vector<int> v = {1, 2, 3}; 2try { 3 v.at(10); // 抛出std::out_of_range 4} catch (const std::out_of_range& e) { 5 std::cerr << "索引越界: " << e.what(); 6} -
运行时错误:
std::runtime_error(如文件打开失败、网络连接中断)cpp1try { 2 std::ifstream file("missing.txt"); 3 if (!file) throw std::runtime_error("文件不存在"); 4} catch (const std::runtime_error& e) { 5 std::cerr << "文件错误: " << e.what(); 6}
2. 用户自定义异常
通过继承std::exception或其子类(如std::runtime_error)创建:
cpp
1class DatabaseError : public std::runtime_error {
2public:
3 DatabaseError(const std::string& msg) : std::runtime_error(msg) {}
4};
5
6try {
7 if (db_connection_failed) throw DatabaseError("连接失败");
8} catch (const DatabaseError& e) {
9 // 执行重连逻辑或回滚事务
10}
二、无法捕获的异常与崩溃场景
以下情况无法通过C++异常处理机制捕获,通常导致程序直接终止:
1. 低级系统错误与信号
- 段错误(Segmentation Fault):如空指针解引用、栈溢出、数组越界访问(未通过
at()等安全函数)。cpp1int* p = nullptr; 2*p = 42; // 触发SIGSEGV,无法通过catch捕获 - 除零错误:整数除零(如
int x = 1/0;)通常由硬件异常触发,导致SIGFPE信号。 - 非法指令:如执行无效的机器指令。
2. 析构函数中的异常
- 若析构函数抛出异常且未被捕获,程序会调用
std::terminate终止。cpp1class Dangerous { 2public: 3 ~Dangerous() { 4 throw std::runtime_error("Oops"); // 析构函数中抛异常 5 } 6}; 7 8try { 9 Dangerous obj; 10} catch (...) { 11 // 无法捕获析构函数中的异常!程序终止 12}
3. noexcept函数抛出异常
- 若声明为
noexcept的函数抛出异常,程序会直接终止。cpp1void safe_func() noexcept { 2 throw std::runtime_error("Error"); // 触发std::terminate 3}
4. 标准库容器的未检查操作
- 如
std::vector::operator[](越界访问未定义行为,不抛异常)、std::string::operator[](同左)。
5. 动态库加载失败
- 操作系统级别的错误(如
dlopen失败)通常通过错误码返回,而非C++异常。
6. C标准库函数的错误
- 如
malloc返回NULL(需手动检查,不抛异常)、fopen失败返回NULL。
三、无法避免崩溃的深层原因
- 硬件异常:如内存访问违规、除零错误由CPU触发信号(如SIGSEGV、SIGFPE),C++异常机制无法捕获。
- 操作系统信号:如SIGKILL(强制终止)、SIGABRT(
abort()触发)无法被用户代码捕获。 - 栈溢出:递归过深或大局部变量导致栈空间耗尽,触发系统级崩溃。
- 多线程竞争:未同步的并发访问导致数据竞争,可能引发不可预测的崩溃。
四、最佳实践与防御性编程
- 资源管理(RAII):使用智能指针(
std::unique_ptr、std::shared_ptr)、锁守卫(std::lock_guard)自动管理资源。 - 输入验证:对外部输入(如用户数据、网络数据)进行严格检查。
- 避免裸指针:优先使用标准库容器和智能指针,减少手动内存管理。
- 信号处理:对无法通过异常捕获的系统信号(如SIGINT、SIGTERM)使用
signal()或sigaction()注册处理函数。 - 栈保护:通过编译器选项(如
-fstack-protector)启用栈溢出检测。 - 日志与监控:记录异常上下文信息,便于诊断问题。
- 单元测试与模糊测试:覆盖异常场景,验证代码健壮性。
五、总结
- 可避免崩溃:通过
try-catch捕获标准库和自定义异常,结合RAII管理资源。 - 无法避免崩溃:硬件异常、信号、析构函数异常、
noexcept违规等系统级错误。 - 关键策略:结合异常处理、错误码、信号处理和防御性编程,构建健壮的C++程序。
通过合理设计,C++程序可以在大多数业务逻辑错误中避免崩溃,但对于系统级错误,需依赖操作系统和硬件的支持,或通过外部监控进程实现自恢复。

1142

被折叠的 条评论
为什么被折叠?



