C++ 是一门强大但复杂的语言,在系统编程与底层开发中广泛使用。异常处理(Exception Handling)机制是保障程序健壮性、提高错误管理效率的重要手段之一。相比传统的错误码返回方式,异常机制具有结构化、自动传播、与资源管理协同的优势。
然而,C++ 的异常机制也因其语义复杂、性能不确定、与 RAII 的耦合等特性,引发了大量争议。本文将从原理到实战,全面剖析 C++ 的异常处理机制,并给出现代 C++ 中的最佳实践。
cpp复制编辑try {
// 可能抛出异常的代码
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << '\n';
} catch (...) {
std::cerr << "Unknown exception caught.\n";
}
cpp复制编辑voiddivide(int a, int b) {
if (b == 0)
throw std::runtime_error("Divide by zero");
std::cout << a / b << '\n';
}
throw int
, throw double
(不推荐)std::exception
的类,如 runtime_error
, logic_error
, out_of_range
等C++ 异常是按类型匹配的,且优先匹配最接近的类型。
cpp复制编辑try {
throw std::out_of_range("Error");
} catch (const std::logic_error& e) {
std::cout << "Logic error\n"; // 匹配,因为是基类
}
当异常被抛出时:
catch
块,则转入处理catch
块,则调用 std::terminate
该过程称为栈展开(stack unwinding),代价较高,因此异常不能用于频繁流程控制。
cpp复制编辑std::exception ├── std::logic_error │ ├── std::invalid_argument │ └── std::out_of_range └── std::runtime_error ├── std::overflow_error └── std::underflow_error
每个异常类都可以调用 what()
方法获得异常描述。
cpp复制编辑voidfoo()noexcept; // 保证不会抛出异常
用于优化、函数选择、并减少不必要的栈展开。
throw()
:C++98 版本的旧语法,不推荐noexcept
: C++11 起的标准方式cpp复制编辑voidsafe()noexcept; // 不抛异常voidrisky()noexcept(false); // 可能抛异常
noexcept
用于模板与 SFINAEcpp复制编辑template<typename T>
voidprocess(T x)noexcept(noexcept(x.doSomething())) {
x.doSomething();
}
cpp复制编辑classMyException : public std::exception {
std::string message;
public:
MyException(const std::string& msg) : message(msg) {}
constchar* what()constnoexceptoverride {
return message.c_str();
}
};
优点:
cpp复制编辑classFileHandle {
FILE* file;
public:
FileHandle(constchar* path) {
file = fopen(path, "r");
if (!file) throw std::runtime_error("open failed");
}
~FileHandle() {
if (file) fclose(file);
}
};
即使 throw
抛出异常,RAII 析构函数也会正确释放资源。
C++ 没有 finally
,但可以用局部类 RAII 替代:
cpp复制编辑structFinally {
std::function<void()> func;
~Finally() { func(); }
};
voidexample() {
Finally cleaner{[] { std::cout << "Cleanup\n"; }};
throw std::runtime_error("Fail");
}
等级 | 含义 |
---|---|
不安全 | 抛出异常后对象可能处于不可用状态 |
基本安全 | 抛出异常后对象状态有效,资源无泄漏 |
强安全 | 抛出异常后对象状态不变(如事务) |
不可抛 | 函数不会抛出异常(如 noexcept ) |
容器操作、构造函数、拷贝赋值需特别注意异常安全性。
std::optional
std::terminate
std::exception_ptr
跨线程传递异常cpp复制编辑std::exception_ptr eptr;
std::thread t([&] {
try {
throw std::runtime_error("thread error");
} catch (...) {
eptr = std::current_exception();
}
});
t.join();
if (eptr) std::rethrow_exception(eptr);
建议 | 说明 |
---|---|
使用 const std::exception& 捕获 | 支持多态 |
使用标准异常类 | 可读性好 |
自定义异常继承自 std::exception | 一致性 |
避免裸 catch(...) | 不可控 |
函数能 noexcept 就加上 | 提高稳定性 |
不要在析构函数中抛异常 | 会触发 terminate() |
尽量使用 RAII 管理资源 | 避免泄漏 |
避免在性能关键路径使用异常 | 有性能成本 |
特性 | 异常机制 | 错误码方式 |
---|---|---|
可读性 | 高,结构清晰 | 容易遗漏判断 |
错误传播 | 自动(栈展开) | 手动传递层层检查 |
异常安全 | 配合 RAII 很强 | 需手动释放 |
性能开销 | 高,适合非频繁路径 | 更高效 |
标准库支持 | 广泛(STL, iostream) | 限 |
结论:异常适用于结构清晰的高层错误处理,错误码适用于性能敏感路径。
异常处理是 C++ 提供的强大机制之一。它使得错误处理从混乱的 if-else
解放出来,成为清晰而结构化的语言特性。
本文回顾要点:
noexcept
提升语义与性能exception_ptr
C++ 异常机制是构建稳定系统的重要工具。熟练掌握它,能让你写出既安全又高效的现代 C++ 代码。
作者: 小菜菜编程, 来源:面包板社区
链接: https://mbb.eet-china.com/blog/uid-me-4114532.html
版权声明:本文为博主原创,未经本人允许,禁止转载!
文章评论(0条评论)
登录后参与讨论