1.1 noexcept 的两种形式
cpp
// 形式1:简单声明(不会抛异常)
void func1() noexcept; // 等同于 noexcept(true)
void func2() noexcept(); // 同上
void func3() noexcept(true); // 明确声明不抛异常
// 形式2:条件性声明
void func4() noexcept(false); // 可能抛异常
void func5() noexcept(表达式); // 根据表达式结果决定
1.2 noexcept 作为操作符
这是模板编程中非常重要的特性:
cpp
#include <iostream>
#include <type_traits>
using namespace std;
class SafeType {
public:
SafeType() noexcept {} // 不抛异常的构造函数
};
class RiskyType {
public:
RiskyType() { throw 1; } // 可能抛异常的构造函数
};
// noexcept操作符:检查表达式是否会抛异常
template<class T>
void templateFunc() noexcept(noexcept(T())) {
T obj; // 根据T()是否noexcept,整个函数也是noexcept的
}
void testNoexceptOperator() {
cout << "SafeType构造是否noexcept: " << noexcept(SafeType()) << endl; // 输出1
cout << "RiskyType构造是否noexcept: " << noexcept(RiskyType()) << endl; // 输出0
// 在模板中应用
cout << "templateFunc<SafeType>是否noexcept: "
<< noexcept(templateFunc<SafeType>()) << endl; // 输出1
}
1.3 实际应用场景(非常重要!)
移动操作必须声明为 noexcept:
cpp
class Vector {
private:
int* data;
size_t size;
public:
// 移动构造函数 - 必须noexcept!
Vector(Vector&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // 偷取资源
other.size = 0;
}
// 移动赋值运算符 - 必须noexcept!
Vector& operator=(Vector&& other) noexcept {
if (this != &other) {
delete[] data; // 释放现有资源
data = other.data; // 偷取资源
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
// 析构函数 - 默认就是noexcept
~Vector() {
delete[] data;
}
};
为什么移动操作要 noexcept?
-
std::vector等容器在重新分配内存时,如果移动构造函数不是noexcept,会 fallback 到拷贝构造 -
这会导致性能损失
二、C++异常机制使用调查
这是工程实践中的宝贵经验,很多是实际项目中的血泪教训。
2.1 禁止使用异常的场景和原因
性能开销:
cpp
// 异常机制的开销来源:
// 1. 异常表(exception table)的维护
// 2. 栈展开时的析构函数调用
// 3. 运行时类型信息(RTTI)查询
// 4. 破坏了编译器的优化机会
// 在性能敏感的场景(如高频交易、嵌入式系统)中避免使用
资源泄漏风险:
cpp
void riskyFunction() {
FILE* file = fopen("data.txt", "r");
int* buffer = new int[1000];
someOperationThatMightThrow(); // 如果这里抛出异常...
fclose(file); // 这行不会执行!
delete[] buffer; // 这行也不会执行!
}
// 即使使用try-catch,也要小心:
void stillRisky() {
FILE* file = fopen("data.txt", "r");
try {
int* buffer = new int[1000];
someOperationThatMightThrow();
delete[] buffer;
} catch (...) {
fclose(file); // 记得关闭文件
throw; // 重新抛出
}
fclose(file); // 正常路径也要关闭
}
2.2 安全编码建议
错误码方式的优势:
cpp
enum class ErrorCode {
Success = 0,
InvalidArgument,
OutOfMemory,
FileNotFound
};
class Result {
private:
ErrorCode code;
std::string message;
public:
Result(ErrorCode c = ErrorCode::Success, const std::string& msg = "")
: code(c), message(msg) {}
bool isSuccess() const { return code == ErrorCode::Success; }
ErrorCode getCode() const { return code; }
const std::string& getMessage() const { return message; }
};
Result processData(const std::string& input) {
if (input.empty()) {
return Result(ErrorCode::InvalidArgument, "输入不能为空");
}
// 处理逻辑...
return Result(ErrorCode::Success);
}
三、标准库异常类的完整体系
3.1 完整的异常类层次
std::exception
├── std::logic_error
│ ├── std::length_error // 长度错误(如vector::resize)
│ ├── std::domain_error // 定义域错误(数学函数)
│ ├── std::out_of_range // 越界访问
│ └── std::invalid_argument // 无效参数
├── std::runtime_error
│ ├── std::range_error // 结果超出值域
│ ├── std::overflow_error // 算术上溢
│ └── std::underflow_error // 算术下溢
├── std::bad_alloc // new失败
├── std::bad_cast // dynamic_cast失败
├── std::bad_typeid // typeid(nullptr)
└── std::bad_exception // 意外异常
3.2 具体使用示例
cpp
#include <stdexcept>
#include <vector>
#include <string>
#include <iostream>
void demonstrateStandardExceptions() {
try {
// 1. length_error
std::vector<int> vec;
vec.resize(vec.max_size() + 1); // 抛出 std::length_error
} catch (const std::length_error& e) {
std::cout << "长度错误: " << e.what() << std::endl;
}
try {
// 2. out_of_range
std::string str = "hello";
char c = str.at(10); // 抛出 std::out_of_range
} catch (const std::out_of_range& e) {
std::cout << "越界访问: " << e.what() << std::endl;
}
try {
// 3. invalid_argument
std::bitset<8> b("01234567"); // 非0/1字符,抛出 std::invalid_argument
} catch (const std::invalid_argument& e) {
std::cout << "无效参数: " << e.what() << std::endl;
}
}
四、异常规范的历史演变
4.1 C++98 的动态异常规范(已废弃)
cpp
// 旧的语法(C++17中已移除)
void oldFunction() throw(int, std::string); // 只能抛出这些类型
void noThrowOld() throw(); // 不抛出任何异常
// 问题:只在运行时检查,性能开销大,实际使用很少
4.2 C++11 的 noexcept(现代替代)
// 现代语法
void modernFunction() noexcept; // 不抛出任何异常
void mayThrowFunction() noexcept(false); // 可能抛出异常
void conditionalNoexcept() noexcept(noexcept(expression));
// 优势:编译期决定,性能更好
五、工程实践建议总结
根据PDF中的"异常机制使用调查",在实际项目中:
5.1 推荐使用异常的场景:
-
接收第三方库抛出的异常
-
处理标准库抛出的异常(如
new失败) -
在应用程序的顶层进行统一的错误处理
5.2 避免使用异常的场景:
-
性能敏感的代码路径
-
嵌入式系统或资源受限环境
-
与C语言的接口边界
-
构造函数中的复杂初始化(考虑使用工厂函数)
5.3 现代C++的最佳实践:
cpp
class ModernClass {
public:
// 1. 默认让析构函数为 noexcept(编译器默认这样做)
~ModernClass() = default;
// 2. 移动操作为 noexcept
ModernClass(ModernClass&&) noexcept = default;
ModernClass& operator=(ModernClass&&) noexcept = default;
// 3. 简单操作标记 noexcept
int getValue() const noexcept { return value; }
// 4. 可能失败的操作返回 Result 或 optional
std::optional<int> tryGetValue() const {
if (isValid()) return value;
return std::nullopt;
}
private:
int value;
};