C++ 异常规范
外观
C++异常规范(Exception Specifications)是C++中用于声明函数可能抛出的异常类型的机制。它允许程序员显式指定函数可能抛出的异常类型,从而提供更好的代码可读性和安全性。然而,随着C++标准的演进(特别是C++11之后),异常规范的用法发生了重大变化,本文将从基础到高级全面讲解这一特性。
概述[编辑 | 编辑源代码]
C++异常规范分为两种形式:
1. 动态异常规范(C++98引入,C++17移除):使用throw(type1, type2)
语法
2. noexcept规范(C++11引入):使用noexcept
或noexcept(expr)
语法
异常规范的主要目的是:
- 作为函数接口的文档
- 允许编译器进行优化
- 在违反规范时调用
std::unexpected()
(动态异常规范)
动态异常规范(已废弃)[编辑 | 编辑源代码]
基本语法[编辑 | 编辑源代码]
返回类型 函数名(参数列表) throw(异常类型列表);
示例:
#include <stdexcept>
void foo(int x) throw(std::runtime_error, std::logic_error) {
if (x < 0) {
throw std::runtime_error("Negative value");
}
if (x > 100) {
throw std::logic_error("Value too large");
}
// 抛出未列出的异常将导致std::unexpected()被调用
}
特殊形式[编辑 | 编辑源代码]
throw()
:表示不抛出任何异常(C++11后可用noexcept
替代)- 无异常规范:可以抛出任何异常
为什么被废弃[编辑 | 编辑源代码]
动态异常规范在实践中存在以下问题: 1. 运行时检查而非编译时检查 2. 性能开销 3. 难以维护(函数签名变更需同步更新异常规范) 4. 与模板结合使用困难
noexcept规范(现代替代方案)[编辑 | 编辑源代码]
C++11引入的noexcept
规范更简单高效,成为推荐做法。
基本语法[编辑 | 编辑源代码]
void func1() noexcept; // 绝不抛出异常
void func2() noexcept(true); // 同noexcept
void func3() noexcept(false); // 可能抛出异常
noexcept运算符[编辑 | 编辑源代码]
noexcept(expr)
可在编译期检查表达式是否可能抛出异常:
template <typename T>
void swap(T& a, T& b) noexcept(noexcept(T(std::move(a))) && noexcept(a.operator=(std::move(b)))) {
// 实现...
}
实际应用示例[编辑 | 编辑源代码]
移动构造函数通常应标记为noexcept
:
class Resource {
int* data;
public:
Resource(Resource&& other) noexcept
: data(other.data) {
other.data = nullptr;
}
// ... 其他成员函数
};
异常规范的影响[编辑 | 编辑源代码]
对代码生成的影响[编辑 | 编辑源代码]
编译器可能基于noexcept
进行优化:
noexcept
函数不需要生成栈展开代码- STL容器对
noexcept
移动操作有特殊处理
标准库使用[编辑 | 编辑源代码]
标准算法通常要求可调用对象为noexcept
:
std::vector<MyType> v;
// 如果MyType的移动构造函数是noexcept,vector.resize可能使用移动而非复制
v.resize(100);
最佳实践[编辑 | 编辑源代码]
1. 优先使用noexcept
而非动态异常规范
2. 移动操作、析构函数、交换操作应尽量标记为noexcept
3. 不应对可能失败的函数(如内存分配)使用noexcept
4. 在接口设计中明确异常保证级别:
* 无异常(noexcept) * 基本异常安全(操作失败时对象状态有效) * 强异常安全(操作失败时状态不变) * 无异常安全(可能泄漏资源)
示例:异常安全类设计[编辑 | 编辑源代码]
class DatabaseConnection {
ConnectionHandle handle;
public:
DatabaseConnection() noexcept : handle(nullptr) {}
void connect(const std::string& url) {
ConnectionHandle new_handle = open_connection(url); // 可能抛出
handle = new_handle; // 仅当成功时修改状态(强异常安全)
}
~DatabaseConnection() noexcept {
if (handle) close_connection(handle);
}
DatabaseConnection(DatabaseConnection&& other) noexcept
: handle(other.handle) {
other.handle = nullptr;
}
DatabaseConnection& operator=(DatabaseConnection&& other) noexcept {
std::swap(handle, other.handle);
return *this;
}
// 禁用复制
DatabaseConnection(const DatabaseConnection&) = delete;
DatabaseConnection& operator=(const DatabaseConnection&) = delete;
};
历史演变[编辑 | 编辑源代码]
数学表示[编辑 | 编辑源代码]
异常规范可以形式化表示为函数签名的一部分:
其中ExceptionSpec可以是:
- (无异常保证)
- (动态规范)
- (无异常)
总结[编辑 | 编辑源代码]
- 现代C++应使用
noexcept
而非动态异常规范 - 异常规范影响代码优化和接口设计
- 合理使用异常规范可以提高代码可靠性和性能
- 析构函数默认应标记为
noexcept
- 移动操作通常应标记为
noexcept
以获得最佳性能