第17章-异常处理
本章全面介绍C++异常处理机制,详细讲解try-catch-throw异常处理语法,介绍标准异常类层次和自定义异常的创建,讲解异常安全性和RAII资源管理技术,演示noexcept说明符的使用,强调异常处理的最佳实践,帮助读者编写健壮的错误处理代码,提高程序的可靠性和可维护性。
·
17.1 异常处理基础
#include <iostream>
#include <stdexcept>
using namespace std;
double divide(double a, double b) {
if (b == 0) {
throw runtime_error("除数不能为零");
}
return a / b;
}
int main() {
try {
cout << divide(10, 2) << endl;
cout << divide(10, 0) << endl;
} catch (const runtime_error& e) {
cout << "捕获异常:" << e.what() << endl;
}
cout << "程序继续执行" << endl;
return 0;
}
17.2 标准异常类
#include <iostream>
#include <exception>
#include <stdexcept>
#include <vector>
using namespace std;
int main() {
try {
// logic_error派生类
throw invalid_argument("无效参数");
// runtime_error派生类
throw out_of_range("越界");
// 数组越界
vector<int> vec(5);
vec.at(10) = 100; // 抛出out_of_range
} catch (const invalid_argument& e) {
cout << "invalid_argument: " << e.what() << endl;
} catch (const out_of_range& e) {
cout << "out_of_range: " << e.what() << endl;
} catch (const exception& e) {
cout << "exception: " << e.what() << endl;
}
return 0;
}
17.3 自定义异常
#include <iostream>
#include <exception>
using namespace std;
class MyException : public exception {
private:
string message;
public:
MyException(const string& msg) : message(msg) {}
const char* what() const noexcept override {
return message.c_str();
}
};
void process(int value) {
if (value < 0) {
throw MyException("值不能为负数");
}
cout << "处理值:" << value << endl;
}
int main() {
try {
process(10);
process(-5);
} catch (const MyException& e) {
cout << "自定义异常:" << e.what() << endl;
}
return 0;
}
17.4 RAII与异常安全
#include <iostream>
#include <memory>
using namespace std;
class Resource {
public:
Resource() { cout << "获取资源" << endl; }
~Resource() { cout << "释放资源" << endl; }
void use() { cout << "使用资源" << endl; }
};
void riskyFunction() {
// 使用智能指针保证异常安全
unique_ptr<Resource> res(new Resource());
res->use();
// 即使抛出异常,析构函数也会被调用
throw runtime_error("发生错误");
}
int main() {
try {
riskyFunction();
} catch (const exception& e) {
cout << "捕获:" << e.what() << endl;
}
return 0;
}
17.2 标准异常类层次结构
17.2.1 完整异常层次图
std::exception (基类)
├── logic_error (逻辑错误 - 程序逻辑问题)
│ ├── invalid_argument (无效参数)
│ ├── domain_error (数学域错误)
│ ├── length_error (长度错误)
│ ├── out_of_range (越界错误)
│ └── future_error (future错误,C++11)
├── runtime_error (运行时错误 - 运行时才能检测)
│ ├── range_error (范围错误)
│ ├── overflow_error (溢出错误)
│ ├── underflow_error (下溢错误)
│ └── system_error (系统错误,C++11)
├── bad_alloc (内存分配失败)
├── bad_cast (dynamic_cast失败)
├── bad_typeid (typeid失败)
├── bad_exception (unexpected异常)
└── bad_function_call (空function调用,C++11)
17.2.2 各类异常详解
#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
using namespace std;
void demonstrateLogicErrors() {
cout << "=== logic_error及其派生类 ===" << endl;
// invalid_argument: 无效参数
try {
throw invalid_argument("这是一个无效的参数");
} catch (const invalid_argument& e) {
cout << "invalid_argument: " << e.what() << endl;
}
// out_of_range: 越界错误
try {
vector<int> vec = {1, 2, 3};
vec.at(10) = 100; // 抛出out_of_range
} catch (const out_of_range& e) {
cout << "out_of_range: " << e.what() << endl;
}
// length_error: 长度错误
try {
string s;
s.resize(s.max_size() + 1); // 超过最大长度
} catch (const length_error& e) {
cout << "length_error: " << e.what() << endl;
}
}
void demonstrateRuntimeErrors() {
cout << "\n=== runtime_error及其派生类 ===" << endl;
// runtime_error: 通用运行时错误
try {
throw runtime_error("运行时发生错误");
} catch (const runtime_error& e) {
cout << "runtime_error: " << e.what() << endl;
}
// overflow_error: 溢出错误
try {
throw overflow_error("数值溢出");
} catch (const overflow_error& e) {
cout << "overflow_error: " << e.what() << endl;
}
// underflow_error: 下溢错误
try {
throw underflow_error("数值下溢");
} catch (const underflow_error& e) {
cout << "underflow_error: " << e.what() << endl;
}
}
void demonstrateMemoryErrors() {
cout << "\n=== 内存相关异常 ===" << endl;
// bad_alloc: 内存分配失败
try {
size_t huge = -1; // 非常大的数
int* ptr = new int[huge]; // 抛出bad_alloc
} catch (const bad_alloc& e) {
cout << "bad_alloc: " << e.what() << endl;
}
}
int main() {
demonstrateLogicErrors();
demonstrateRuntimeErrors();
demonstrateMemoryErrors();
return 0;
}
17.2.3 自定义异常类的最佳实践
#include <iostream>
#include <exception>
#include <string>
using namespace std;
// 方式1:继承std::exception
class MyException : public exception {
private:
string message;
public:
explicit MyException(const string& msg) : message(msg) {}
const char* what() const noexcept override {
return message.c_str();
}
};
// 方式2:继承std::runtime_error(推荐)
class FileException : public runtime_error {
private:
string filename;
public:
FileException(const string& msg, const string& file)
: runtime_error(msg), filename(file) {}
const string& getFilename() const { return filename; }
};
// 方式3:异常类层次
class NetworkException : public runtime_error {
public:
using runtime_error::runtime_error;
};
class ConnectionException : public NetworkException {
public:
using NetworkException::NetworkException;
};
class TimeoutException : public NetworkException {
public:
using NetworkException::NetworkException;
};
void testCustomExceptions() {
// 测试MyException
try {
throw MyException("自定义异常发生");
} catch (const MyException& e) {
cout << "捕获:" << e.what() << endl;
}
// 测试FileException
try {
throw FileException("文件打开失败", "data.txt");
} catch (const FileException& e) {
cout << "捕获:" << e.what()
<< ", 文件:" << e.getFilename() << endl;
}
// 测试异常类层次
try {
throw TimeoutException("连接超时");
} catch (const TimeoutException& e) {
cout << "超时异常:" << e.what() << endl;
} catch (const NetworkException& e) {
cout << "网络异常:" << e.what() << endl;
}
}
int main() {
testCustomExceptions();
return 0;
}
17.3 异常捕获的高级用法
17.3.1 多重catch块的顺序
#include <iostream>
#include <stdexcept>
using namespace std;
void demonstrateCatchOrder() {
try {
throw out_of_range("越界错误");
}
catch (const out_of_range& e) {
// 具体异常应该放在前面
cout << "捕获out_of_range: " << e.what() << endl;
}
catch (const logic_error& e) {
// 基类异常放在后面
cout << "捕获logic_error: " << e.what() << endl;
}
catch (const exception& e) {
// 最通用的异常放在最后
cout << "捕获exception: " << e.what() << endl;
}
}
int main() {
demonstrateCatchOrder();
return 0;
}
重要规则:
- 派生类异常必须在基类异常之前捕获
- 否则派生类的catch块永远不会被执行
17.3.2 捕获所有异常
#include <iostream>
using namespace std;
void riskyFunction() {
throw 42; // 抛出int类型异常
}
int main() {
try {
riskyFunction();
}
catch (const exception& e) {
cout << "标准异常:" << e.what() << endl;
}
catch (...) {
// 捕获所有类型的异常
cout << "捕获未知类型异常" << endl;
}
return 0;
}
17.3.3 重新抛出异常
#include <iostream>
#include <stdexcept>
using namespace std;
void innerFunction() {
throw runtime_error("内部错误");
}
void middleFunction() {
try {
innerFunction();
} catch (const exception& e) {
cout << "middleFunction捕获: " << e.what() << endl;
// 重新抛出原异常
throw;
}
}
void outerFunction() {
try {
middleFunction();
} catch (const exception& e) {
cout << "outerFunction捕获: " << e.what() << endl;
}
}
int main() {
outerFunction();
return 0;
}
throw vs throw e的区别:
try {
// ...
} catch (const exception& e) {
throw; // ✅ 正确:重新抛出原始异常,保留类型信息
// throw e; // ❌ 不好:会对象切片,丢失派生类信息
}
17.3.4 嵌套异常(C++11)
#include <iostream>
#include <exception>
#include <stdexcept>
using namespace std;
void processFile(const string& filename) {
try {
throw runtime_error("文件读取失败");
} catch (...) {
// 抛出新异常,同时保留原异常
throw_with_nested(logic_error("处理文件时出错: " + filename));
}
}
void printException(const exception& e, int level = 0) {
cout << string(level, ' ') << "异常: " << e.what() << endl;
try {
rethrow_if_nested(e);
} catch (const exception& nested) {
printException(nested, level + 2);
}
}
int main() {
try {
processFile("data.txt");
} catch (const exception& e) {
printException(e);
}
return 0;
}
17.4 异常安全性保证
17.4.1 异常安全性的三个级别
#include <iostream>
#include <vector>
#include <string>
#include <memory>
using namespace std;
// 1. 基本保证(Basic Guarantee)
// - 不会泄漏资源
// - 对象处于有效状态(但可能不是原始状态)
class BasicGuarantee {
private:
vector<int> data;
int count;
public:
void add(int value) {
data.push_back(value); // 可能抛出异常
count++; // 如果上面抛异常,count没有增加
}
// 问题:data和count可能不一致
};
// 2. 强保证(Strong Guarantee)
// - 操作成功或失败
// - 失败时状态完全不变
class StrongGuarantee {
private:
vector<int> data;
int count;
public:
void add(int value) {
auto temp = data; // 先复制
temp.push_back(value); // 在副本上操作
data = move(temp); // 只有成功才修改
count++;
}
// 保证:要么成功,要么状态完全不变
};
// 3. 不抛异常保证(Nothrow Guarantee)
// - 保证不会抛出异常
class NothrowGuarantee {
private:
int* ptr;
public:
NothrowGuarantee() : ptr(nullptr) {}
~NothrowGuarantee() noexcept {
delete ptr; // 析构函数应该noexcept
}
void swap(NothrowGuarantee& other) noexcept {
std::swap(ptr, other.ptr); // swap通常不抛异常
}
};
17.4.2 RAII与异常安全
#include <iostream>
#include <fstream>
#include <memory>
#include <mutex>
using namespace std;
// ❌ 不安全的代码
void unsafeFunction() {
int* ptr = new int[100];
// 如果这里抛出异常,内存泄漏!
if (/* 某个条件 */ false) {
throw runtime_error("错误");
}
delete[] ptr;
}
// ✅ 使用RAII的安全代码
void safeFunction() {
unique_ptr<int[]> ptr(new int[100]);
// 即使抛出异常,unique_ptr也会自动释放内存
if (/* 某个条件 */ false) {
throw runtime_error("错误");
}
}
// RAII封装文件操作
class FileHandler {
private:
ifstream file;
string filename;
public:
FileHandler(const string& name) : filename(name) {
file.open(filename);
if (!file.is_open()) {
throw runtime_error("无法打开文件: " + filename);
}
}
~FileHandler() {
if (file.is_open()) {
file.close();
}
}
string readLine() {
string line;
if (!getline(file, line)) {
throw runtime_error("读取失败");
}
return line;
}
};
// RAII封装互斥锁
void threadSafeFunction(mutex& mtx, int& shared_data) {
lock_guard<mutex> lock(mtx); // RAII管理锁
// 即使抛出异常,锁也会自动释放
shared_data++;
if (shared_data > 100) {
throw runtime_error("数据超限");
}
}
int main() {
try {
safeFunction();
FileHandler fh("data.txt");
cout << fh.readLine() << endl;
} catch (const exception& e) {
cout << "错误: " << e.what() << endl;
}
return 0;
}
17.4.3 Copy-and-Swap惯用法
#include <iostream>
#include <algorithm>
using namespace std;
class SafeClass {
private:
int* data;
size_t size;
public:
SafeClass(size_t s) : data(new int[s]), size(s) {}
SafeClass(const SafeClass& other)
: data(new int[other.size]), size(other.size) {
copy(other.data, other.data + size, data);
}
~SafeClass() {
delete[] data;
}
// 使用Copy-and-Swap实现强异常安全保证
SafeClass& operator=(const SafeClass& other) {
SafeClass temp(other); // 先复制(可能抛异常)
swap(temp); // 交换(不抛异常)
return *this; // temp析构时释放旧数据
}
void swap(SafeClass& other) noexcept {
std::swap(data, other.data);
std::swap(size, other.size);
}
void print() const {
for (size_t i = 0; i < size; ++i) {
cout << data[i] << " ";
}
cout << endl;
}
};
int main() {
SafeClass obj1(5);
SafeClass obj2(3);
obj1 = obj2; // 强异常安全:要么成功,要么obj1不变
return 0;
}
17.5 函数try块
17.5.1 构造函数的函数try块
#include <iostream>
#include <stdexcept>
using namespace std;
class Resource {
public:
Resource() {
cout << "Resource构造" << endl;
throw runtime_error("Resource构造失败");
}
~Resource() {
cout << "Resource析构" << endl;
}
};
class MyClass {
private:
Resource res;
int value;
public:
// 函数try块:捕获初始化列表中的异常
MyClass(int v)
try : res(), value(v) {
cout << "MyClass构造函数体" << endl;
}
catch (const exception& e) {
cout << "捕获初始化异常: " << e.what() << endl;
// 注意:这里会自动重新抛出异常
// 不能阻止异常传播
}
};
int main() {
try {
MyClass obj(10);
} catch (const exception& e) {
cout << "外部捕获: " << e.what() << endl;
}
return 0;
}
重要提示:
- 构造函数的函数try块会自动重新抛出异常
- 主要用于记录日志或清理资源
- 不能完全阻止异常传播
17.5.2 普通函数的函数try块
#include <iostream>
using namespace std;
int divide(int a, int b)
try {
if (b == 0) {
throw invalid_argument("除数为零");
}
return a / b;
}
catch (const exception& e) {
cout << "函数try块捕获: " << e.what() << endl;
return 0; // 可以返回默认值
}
int main() {
cout << divide(10, 2) << endl; // 5
cout << divide(10, 0) << endl; // 0
return 0;
}
17.6 noexcept说明符详解
17.6.1 基本用法
#include <iostream>
using namespace std;
// 声明不会抛出异常
void safeFunc() noexcept {
// 这个函数保证不抛异常
cout << "安全函数" << endl;
}
// 条件noexcept
template<typename T>
void conditionalFunc(T value) noexcept(noexcept(value.print())) {
value.print();
}
int main() {
safeFunc();
// 检查函数是否noexcept
cout << "safeFunc是noexcept: "
<< noexcept(safeFunc()) << endl; // 1 (true)
return 0;
}
17.6.2 noexcept与移动语义
#include <iostream>
#include <vector>
using namespace std;
class MyClass {
public:
MyClass() { cout << "构造" << endl; }
// 移动构造应该声明为noexcept
MyClass(MyClass&&) noexcept {
cout << "移动构造" << endl;
}
// 拷贝构造
MyClass(const MyClass&) {
cout << "拷贝构造" << endl;
}
};
int main() {
vector<MyClass> vec;
vec.reserve(2);
vec.push_back(MyClass()); // 第一个元素
cout << "添加第二个元素(可能触发扩容):" << endl;
vec.push_back(MyClass()); // 扩容时会使用移动构造
return 0;
}
重要:
- vector扩容时,如果移动构造是noexcept,使用移动
- 否则使用拷贝(为了异常安全)
17.6.3 析构函数与noexcept
class MyClass {
public:
// ✅ 析构函数默认是noexcept
~MyClass() {
// 不要在析构函数中抛出异常
}
// ❌ 不要这样做
~MyClass() noexcept(false) {
throw runtime_error("析构函数抛异常");
// 危险!可能导致程序终止
}
};
规则:
- 析构函数默认是noexcept(true)
- 永远不要在析构函数中抛出异常
17.7 异常与性能
17.7.1 异常的开销
#include <iostream>
#include <chrono>
using namespace std;
using namespace chrono;
// 方法1:使用异常
int divideWithException(int a, int b) {
if (b == 0) {
throw invalid_argument("除数为零");
}
return a / b;
}
// 方法2:使用错误码
int divideWithErrorCode(int a, int b, bool& success) {
if (b == 0) {
success = false;
return 0;
}
success = true;
return a / b;
}
void benchmarkException() {
auto start = high_resolution_clock::now();
int result = 0;
for (int i = 0; i < 1000000; ++i) {
try {
result += divideWithException(10, 2);
} catch (...) {
// 不会执行
}
}
auto end = high_resolution_clock::now();
auto duration = duration_cast<microseconds>(end - start);
cout << "异常方式(无异常)耗时: " << duration.count() << " 微秒" << endl;
}
void benchmarkErrorCode() {
auto start = high_resolution_clock::now();
int result = 0;
bool success;
for (int i = 0; i < 1000000; ++i) {
result += divideWithErrorCode(10, 2, success);
}
auto end = high_resolution_clock::now();
auto duration = duration_cast<microseconds>(end - start);
cout << "错误码方式耗时: " << duration.count() << " 微秒" << endl;
}
int main() {
benchmarkException();
benchmarkErrorCode();
return 0;
}
性能建议:
- 正常路径无异常时,异常机制开销很小
- 抛出异常时,开销较大
- 不要将异常用于正常流程控制
17.7.2 异常vs错误码的选择
// ✅ 适合使用异常的场景
class File {
public:
File(const string& filename) {
// 构造失败无法返回错误码,使用异常
if (!open(filename)) {
throw runtime_error("打开文件失败");
}
}
};
// ✅ 适合使用错误码的场景
bool parseInteger(const string& str, int& result) {
// 解析失败是常见情况,使用错误码
try {
result = stoi(str);
return true;
} catch (...) {
return false;
}
}
选择原则:
- 错误是异常情况 → 使用异常
- 错误是正常情况 → 使用错误码
- 无法通过返回值报告错误(构造函数) → 使用异常
- 需要跨越多层函数传播错误 → 使用异常
- 追求极致性能的关键路径 → 使用错误码
17.8 异常处理最佳实践
17.8.1 异常类设计原则
#include <iostream>
#include <exception>
#include <string>
using namespace std;
// ✅ 好的异常类设计
class DatabaseException : public runtime_error {
private:
int errorCode;
string sqlStatement;
public:
DatabaseException(const string& msg, int code, const string& sql)
: runtime_error(msg), errorCode(code), sqlStatement(sql) {}
int getErrorCode() const { return errorCode; }
const string& getSqlStatement() const { return sqlStatement; }
// 提供详细的错误信息
string getDetailedMessage() const {
return string(what()) + " (错误码: " + to_string(errorCode) +
", SQL: " + sqlStatement + ")";
}
};
void testDatabaseException() {
try {
throw DatabaseException("查询失败", 1064,
"SELECT * FROM users WHERE id = ?");
} catch (const DatabaseException& e) {
cout << e.getDetailedMessage() << endl;
}
}
int main() {
testDatabaseException();
return 0;
}
17.8.2 异常安全的代码模式
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
// 模式1:使用智能指针管理资源
void pattern1() {
unique_ptr<int[]> data(new int[100]);
// 自动管理,异常安全
}
// 模式2:在一个语句中只做一件事
void pattern2() {
// ❌ 不好
// processWidget(shared_ptr<Widget>(new Widget()), priority());
// 如果priority()抛异常,Widget可能泄漏
// ✅ 好
shared_ptr<Widget> pw(new Widget());
processWidget(pw, priority());
}
// 模式3:使用STL容器
void pattern3() {
vector<int> vec;
vec.push_back(1); // 自动管理内存,异常安全
}
17.8.3 日志与异常
#include <iostream>
#include <fstream>
#include <string>
#include <ctime>
using namespace std;
class Logger {
private:
ofstream logFile;
public:
Logger(const string& filename) {
logFile.open(filename, ios::app);
}
void log(const string& level, const string& message) {
time_t now = time(nullptr);
logFile << ctime(&now) << " [" << level << "] "
<< message << endl;
}
void logException(const exception& e) {
log("ERROR", string("异常: ") + e.what());
}
};
void businessLogic() {
Logger logger("app.log");
try {
// 业务逻辑
throw runtime_error("业务错误");
} catch (const exception& e) {
logger.logException(e);
throw; // 重新抛出以便上层处理
}
}
int main() {
try {
businessLogic();
} catch (const exception& e) {
cout << "最终捕获: " << e.what() << endl;
}
return 0;
}
17.9 实战练习
练习1:实现安全的资源管理类
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class SafeFileReader {
private:
ifstream file;
string filename;
public:
explicit SafeFileReader(const string& name) : filename(name) {
file.open(filename);
if (!file.is_open()) {
throw runtime_error("无法打开文件: " + filename);
}
}
~SafeFileReader() {
if (file.is_open()) {
file.close();
}
}
// 禁止拷贝
SafeFileReader(const SafeFileReader&) = delete;
SafeFileReader& operator=(const SafeFileReader&) = delete;
string readLine() {
string line;
if (!getline(file, line)) {
if (file.eof()) {
throw runtime_error("文件结束");
} else {
throw runtime_error("读取错误");
}
}
return line;
}
vector<string> readAll() {
vector<string> lines;
string line;
while (getline(file, line)) {
lines.push_back(line);
}
return lines;
}
};
int main() {
try {
SafeFileReader reader("test.txt");
auto lines = reader.readAll();
for (const auto& line : lines) {
cout << line << endl;
}
} catch (const exception& e) {
cout << "错误: " << e.what() << endl;
}
return 0;
}
练习2:实现带重试机制的函数
#include <iostream>
#include <stdexcept>
#include <chrono>
#include <thread>
using namespace std;
template<typename Func>
auto retryOnFailure(Func func, int maxRetries = 3,
int delayMs = 100) -> decltype(func()) {
for (int attempt = 0; attempt < maxRetries; ++attempt) {
try {
return func();
} catch (const exception& e) {
if (attempt == maxRetries - 1) {
// 最后一次尝试失败,重新抛出
throw;
}
cout << "尝试 " << (attempt + 1) << " 失败: "
<< e.what() << ", 重试中..." << endl;
// 等待后重试
this_thread::sleep_for(chrono::milliseconds(delayMs));
}
}
}
// 模拟不稳定的网络请求
int unstableNetworkRequest() {
static int callCount = 0;
callCount++;
if (callCount < 3) {
throw runtime_error("网络连接失败");
}
return 200; // 成功
}
int main() {
try {
int result = retryOnFailure(unstableNetworkRequest);
cout << "请求成功,状态码: " << result << endl;
} catch (const exception& e) {
cout << "最终失败: " << e.what() << endl;
}
return 0;
}
练习3:实现异常安全的栈
#include <iostream>
#include <stdexcept>
#include <memory>
using namespace std;
template<typename T>
class SafeStack {
private:
struct Node {
T data;
unique_ptr<Node> next;
Node(const T& value) : data(value), next(nullptr) {}
};
unique_ptr<Node> head;
size_t count;
public:
SafeStack() : head(nullptr), count(0) {}
void push(const T& value) {
auto newNode = make_unique<Node>(value);
newNode->next = move(head);
head = move(newNode);
count++;
}
T pop() {
if (!head) {
throw runtime_error("栈为空");
}
T value = head->data;
head = move(head->next);
count--;
return value;
}
const T& top() const {
if (!head) {
throw runtime_error("栈为空");
}
return head->data;
}
bool empty() const {
return head == nullptr;
}
size_t size() const {
return count;
}
};
int main() {
try {
SafeStack<int> stack;
stack.push(1);
stack.push(2);
stack.push(3);
while (!stack.empty()) {
cout << stack.pop() << " ";
}
cout << endl;
// stack.pop(); // 抛出异常
} catch (const exception& e) {
cout << "错误: " << e.what() << endl;
}
return 0;
}
参考资料
-
书籍
- Stanley B. Lippman. 《C++ Primer》(第5版) - 第18章 异常处理
- Bjarne Stroustrup. 《C++程序设计语言》(第4版) - 异常处理
- Scott Meyers. 《More Effective C++》 - 条款9-15 异常
- Herb Sutter. 《Exceptional C++》 - 异常安全性
-
在线文档
-
核心概念
-
技术文章
更多推荐

所有评论(0)