throw与noexcept对比
概念
throw是c++98/c++03时代就存在的说明符;noexcept关键字是c++11引入的关键字,这个关键字的引入主要用于替代throw说明符。
他们的作用类似都是用于说明一个函数是否抛出异常。
基本用法
throw基本用法
// 旧式异常说明
void oldFunction() throw(); // 保证不抛出异常
void oldFunction2() throw(std::exception); // 可能抛出 std::exception
noexcept基本用法
// 1. 函数声明中的 noexcept
void func1() noexcept; // 保证不会抛出异常
void func2() noexcept(true); // 等同于 noexcept
void func3() noexcept(false); // 可能抛出异常
void func4() noexcept(condition); // 条件性指定是否会抛出异常// 2. noexcept 操作符
bool will_throw = noexcept(func1()); // 检查表达式是否会抛出异常
性能对比
- throw在运行时生效
- noexcept在编译阶段,在运行阶段基本无开销。
那么如何理解上面的这段话呢?请看下面的例子:
// 使用 throw()
void example() throw() {
try {
// 函数体
throw std::runtime_error("error"); // 抛出异常
} catch (...) {
// 如果抛出异常,运行时会:
// 1. 调用 std::unexpected()
// 2. 默认情况下,std::unexpected 会调用 std::terminate()
// 3. 程序终止
}
}// 实际上编译器会生成类似这样的代码
void example_impl() {
try {
// 原始函数体
} catch (...) {
std::unexpected(); // 运行时检查违反异常规范
}
}
通过上面的例子可以看到:编译器对于throw修饰的函数会在原函数中生成一段代码,这段代码在运行时如果检测到异常,那么就会调用编译器插入的函数std::unexcept(),并最终调用std::terminate退出程序。
那么noexcept是什么行为呢?请看下面的例子:
// 编译器会在编译时进行静态分析
void example() noexcept {
throw std::runtime_error("error"); // 编译错误:不允许在 noexcept 函数中抛出异常
}// 如果运行时确实抛出了异常(比如通过函数指针调用)
void what_happens() noexcept {
void (*fp)() = []() { throw std::runtime_error("error"); };
fp(); // 如果执行,直接调用 std::terminate()
}
通过上面的例子可以看到,在编译阶段就会完成与throw类似的功能。但是并不是在通过在编译阶段插入了一些函数语句来实现的,而是在编译阶段直接报错的方式。
使用建议
- 在现代c++工程中,建议使用noexcept
- 应避免使用throw(),它在 C++17 中被规定为等同于
noexcept
,在 C++20 中被弃用
最佳实践
-
应该使用 noexcept 的场合:
- 移动构造函数和移动赋值运算符
- 析构函数(默认就是)
- 简单的getter/setter
- 不会失败的操作
-
不应该使用 noexcept 的场合:
- 可能分配内存的操作
- 可能抛出异常的系统调用
- 复杂的操作序列