第25章 异常处理基础

异常处理概述

异常处理是一种处理程序运行时错误的机制,它允许程序在遇到错误时,跳转到专门的错误处理代码,而不是直接崩溃。C++的异常处理机制提供了一种结构化的方式来处理错误,使得错误处理代码与正常业务逻辑分离,提高了代码的可读性和可维护性。

异常处理的基本概念

  • 异常:程序运行时发生的错误情况,如除零、数组越界、内存不足等
  • 抛出异常:当程序遇到错误时,使用throw关键字抛出异常
  • 捕获异常:使用try-catch块捕获并处理异常
  • 异常传播:如果异常在当前函数中没有被捕获,它会沿着调用栈向上传播,直到被捕获或导致程序终止

异常处理的优势

  1. 错误处理与业务逻辑分离:异常处理代码集中在catch块中,使正常业务逻辑更加清晰
  2. 分层错误处理:不同层次的代码可以处理不同类型的异常
  3. 资源自动释放:结合RAII(资源获取即初始化),确保即使发生异常也能正确释放资源
  4. 错误信息传递:异常对象可以携带详细的错误信息
  5. 强制错误处理:某些异常必须被处理,否则会导致程序终止

异常的基本语法

抛出异常

使用throw关键字抛出异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <stdexcept>

void divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("Division by zero");
}
std::cout << "Result: " << a / b << std::endl;
}

int main() {
try {
divide(10, 0);
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}

捕获异常

使用try-catch块捕获异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <stdexcept>
#include <string>

void process(int value) {
if (value < 0) {
throw std::invalid_argument("Value must be non-negative");
} else if (value > 100) {
throw std::out_of_range("Value must be less than or equal to 100");
}
std::cout << "Processing value: " << value << std::endl;
}

int main() {
try {
process(-1);
} catch (const std::invalid_argument& e) {
std::cout << "Invalid argument: " << e.what() << std::endl;
} catch (const std::out_of_range& e) {
std::cout << "Out of range: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "General error: " << e.what() << std::endl;
}

try {
process(200);
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}

return 0;
}

异常规格说明

C++11之前,使用异常规格说明(exception specification)来声明函数可能抛出的异常类型:

1
2
3
// C++11之前的异常规格说明
void func() throw(std::runtime_error, std::logic_error); // 可能抛出这两种异常
void func() throw(); // 不抛出任何异常

C++11引入了noexcept说明符,替代了异常规格说明:

1
2
3
4
5
6
7
// C++11及以后的noexcept说明符
void func() noexcept; // 不抛出任何异常
void func() noexcept(false); // 可能抛出异常

// 条件noexcept
template <typename T>
void swap(T& a, T& b) noexcept(noexcept(std::swap(a, b)));

异常安全保证

异常安全是指当异常发生时,程序能够保持一致的状态,不会泄漏资源,也不会破坏数据结构。C++中,异常安全保证通常分为三个级别:

1. 基本异常安全(Basic Exception Safety)

  • 保证:当异常发生时,程序不会泄漏资源,数据结构保持有效状态,但可能不是原始状态
  • 示例:使用智能指针管理内存,确保即使发生异常也能释放内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <memory>
#include <vector>

void processData(std::vector<int>& data) {
std::vector<int> temp;

// 复制数据到临时向量
for (int value : data) {
temp.push_back(value * 2);
}

// 如果上面的操作抛出异常,data不会被修改
data.swap(temp); // 无异常操作
}

2. 强异常安全(Strong Exception Safety)

  • 保证:当异常发生时,程序状态完全回滚到操作前的状态,就像操作从未发生过一样
  • 示例:使用复制-交换技术(Copy-and-Swap)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <algorithm>

class ResourceManager {
private:
int* data;
size_t size;

public:
ResourceManager(size_t s) : size(s), data(new int[s]()) {}

// 复制构造函数
ResourceManager(const ResourceManager& other)
: size(other.size), data(new int[other.size]) {
std::copy(other.data, other.data + other.size, data);
}

// 析构函数
~ResourceManager() {
delete[] data;
}

// 复制-交换赋值运算符
ResourceManager& operator=(ResourceManager other) {
swap(*this, other);
return *this;
}

// 交换函数
friend void swap(ResourceManager& a, ResourceManager& b) {
std::swap(a.data, b.data);
std::swap(a.size, b.size);
}

// 修改数据的操作(强异常安全)
void resize(size_t newSize) {
ResourceManager temp(newSize);
std::copy(data, data + std::min(size, newSize), temp.data);
swap(*this, temp); // 无异常操作
}
};

3. 无异常保证(No-Throw Guarantee)

  • 保证:操作永远不会抛出异常
  • 示例:使用noexcept说明符标记的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <vector>

// 无异常保证的交换函数
void swapValues(int& a, int& b) noexcept {
int temp = a;
a = b;
b = temp;
}

// 无异常保证的访问函数
template <typename T>
const T& getElement(const std::vector<T>& vec, size_t index) noexcept {
// 假设index已经被验证为有效
return vec[index];
}

异常传播

异常传播机制

当异常在函数中抛出但未被捕获时,它会沿着调用栈向上传播,直到找到匹配的catch块。这个过程称为栈展开(Stack Unwinding)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <stdexcept>

void func3() {
throw std::runtime_error("Error in func3");
}

void func2() {
func3(); // 异常传播到这里
}

void func1() {
func2(); // 异常传播到这里
}

int main() {
try {
func1(); // 异常传播到这里
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}

return 0;
}

栈展开过程

当异常发生时,栈展开过程会:

  1. 销毁当前函数中已经构造的局部对象
  2. 返回到调用函数
  3. 重复上述过程,直到找到匹配的catch块
  4. 如果没有找到catch块,调用std::terminate()终止程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>

class MyClass {
public:
MyClass(int id) : id(id) {
std::cout << "Constructor " << id << std::endl;
}

~MyClass() {
std::cout << "Destructor " << id << std::endl;
}

private:
int id;
};

void func() {
MyClass c3(3);
throw 42;
MyClass c4(4); // 不会执行
}

void caller() {
MyClass c2(2);
func();
MyClass c5(5); // 不会执行
}

int main() {
try {
MyClass c1(1);
caller();
MyClass c6(6); // 不会执行
} catch (int e) {
std::cout << "Caught: " << e << std::endl;
}

MyClass c7(7);
return 0;
}

// 输出:
// Constructor 1
// Constructor 2
// Constructor 3
// Destructor 3
// Destructor 2
// Destructor 1
// Caught: 42
// Constructor 7
// Destructor 7

异常与RAII

RAII简介

RAII(Resource Acquisition Is Initialization)是一种C++编程技术,它通过对象的构造和析构来管理资源,确保资源在使用完毕后被正确释放。

RAII与异常安全

RAII是实现异常安全的关键技术,它确保无论是否发生异常,资源都能被正确释放:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <fstream>
#include <stdexcept>

// RAII包装器用于文件
class FileGuard {
private:
std::ofstream& file;

public:
explicit FileGuard(std::ofstream& f) : file(f) {
if (!file.is_open()) {
throw std::runtime_error("Failed to open file");
}
}

~FileGuard() {
file.close();
std::cout << "File closed" << std::endl;
}

// 禁止复制和移动
FileGuard(const FileGuard&) = delete;
FileGuard& operator=(const FileGuard&) = delete;
};

void writeToFile(const std::string& filename, const std::string& content) {
std::ofstream file(filename);
FileGuard guard(file); // 确保文件被关闭

// 模拟异常
throw std::runtime_error("Error during write");

file << content << std::endl;
}

int main() {
try {
writeToFile("test.txt", "Hello, RAII!");
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}

// 输出:
// File closed
// Error: Error during write

智能指针与异常

智能指针是RAII的典型应用,它们确保即使发生异常,动态分配的内存也能被正确释放:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <memory>
#include <stdexcept>

void processData() {
// 使用智能指针管理内存
std::unique_ptr<int> data(new int[1000]);

// 模拟异常
throw std::runtime_error("Processing error");

// 即使发生异常,智能指针也会自动释放内存
}

// C++14及以后,推荐使用make_unique
void processDataModern() {
auto data = std::make_unique<int[]>(1000);

// 模拟异常
throw std::runtime_error("Processing error");
}

int main() {
try {
processData();
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}

try {
processDataModern();
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}

return 0;
}

自定义异常类

自定义异常类的基本结构

自定义异常类通常继承自std::exception或其派生类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdexcept>
#include <string>

class DatabaseError : public std::runtime_error {
public:
DatabaseError(const std::string& message, int errorCode)
: std::runtime_error(message), errorCode(errorCode) {}

int getErrorCode() const { return errorCode; }

private:
int errorCode;
};

class NetworkError : public std::runtime_error {
public:
NetworkError(const std::string& message, int statusCode)
: std::runtime_error(message), statusCode(statusCode) {}

int getStatusCode() const { return statusCode; }

private:
int statusCode;
};

void connectToDatabase() {
// 模拟数据库错误
throw DatabaseError("Failed to connect to database", 5001);
}

void connectToServer() {
// 模拟网络错误
throw NetworkError("Failed to connect to server", 404);
}

int main() {
try {
connectToDatabase();
} catch (const DatabaseError& e) {
std::cout << "Database error: " << e.what()
<< ", Code: " << e.getErrorCode() << std::endl;
} catch (const std::exception& e) {
std::cout << "General error: " << e.what() << std::endl;
}

try {
connectToServer();
} catch (const NetworkError& e) {
std::cout << "Network error: " << e.what()
<< ", Status: " << e.getStatusCode() << std::endl;
} catch (const std::exception& e) {
std::cout << "General error: " << e.what() << std::endl;
}

return 0;
}

异常类的最佳实践

  1. 继承自标准异常类:便于与标准异常处理机制集成
  2. 提供有意义的错误信息:在异常对象中存储详细的错误信息
  3. 实现移动构造函数:减少异常对象的复制开销
  4. 保持异常类简单:只包含必要的成员变量和方法
  5. 使用异常层次结构:创建有层次的异常类体系,便于分类处理

异常处理的最佳实践

1. 只在特殊情况下使用异常

不要将异常用于常规的控制流,只在真正的异常情况下使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 不好的做法:使用异常进行控制流
int findElement(const std::vector<int>& vec, int value) {
for (size_t i = 0; i < vec.size(); i++) {
if (vec[i] == value) {
return i;
}
}
throw std::runtime_error("Element not found");
}

// 好的做法:使用返回值
std::optional<size_t> findElement(const std::vector<int>& vec, int value) {
for (size_t i = 0; i < vec.size(); i++) {
if (vec[i] == value) {
return i;
}
}
return std::nullopt;
}

2. 捕获适当的异常类型

只捕获你能够处理的异常类型,避免捕获所有异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 不好的做法:捕获所有异常
void process() {
try {
// 代码
} catch (...) {
std::cout << "Something went wrong" << std::endl;
}
}

// 好的做法:捕获特定类型的异常
void process() {
try {
// 代码
} catch (const std::invalid_argument& e) {
std::cout << "Invalid argument: " << e.what() << std::endl;
} catch (const std::runtime_error& e) {
std::cout << "Runtime error: " << e.what() << std::endl;
}
}

3. 重新抛出异常时保持原始异常信息

当需要重新抛出异常时,使用空的throw语句,保持原始异常信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void processFile(const std::string& filename) {
try {
// 打开文件并处理
std::ifstream file(filename);
if (!file) {
throw std::runtime_error("Failed to open file");
}
// 处理文件...
} catch (const std::exception& e) {
// 记录错误
std::cerr << "Error in processFile: " << e.what() << std::endl;
// 重新抛出异常,保持原始异常信息
throw;
}
}

4. 使用异常层次结构

创建有层次的异常类体系,便于分类处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 基础异常类
class AppException : public std::exception {
public:
explicit AppException(const std::string& message) : message(message) {}

const char* what() const noexcept override {
return message.c_str();
}

private:
std::string message;
};

// 业务逻辑异常
class BusinessException : public AppException {
public:
explicit BusinessException(const std::string& message) : AppException(message) {}
};

// 数据访问异常
class DataAccessException : public AppException {
public:
explicit DataAccessException(const std::string& message) : AppException(message) {}
};

// 使用异常层次结构
void processOrder() {
try {
// 业务逻辑
throw BusinessException("Order processing failed");
} catch (const DataAccessException& e) {
std::cout << "Data access error: " << e.what() << std::endl;
} catch (const BusinessException& e) {
std::cout << "Business error: " << e.what() << std::endl;
} catch (const AppException& e) {
std::cout << "Application error: " << e.what() << std::endl;
}
}

5. 异常处理与错误码的结合

在某些情况下,特别是与C API交互或需要向后兼容时,可能需要结合使用异常和错误码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 错误码定义
enum class ErrorCode {
SUCCESS = 0,
INVALID_ARGUMENT = 1,
NETWORK_ERROR = 2,
DATABASE_ERROR = 3
};

// 异常类
class AppException : public std::runtime_error {
public:
AppException(ErrorCode code, const std::string& message)
: std::runtime_error(message), code(code) {}

ErrorCode getErrorCode() const { return code; }

private:
ErrorCode code;
};

// 同时支持异常和错误码的函数
ErrorCode processWithErrorCodes(int value, std::string& errorMessage) {
try {
if (value < 0) {
throw AppException(ErrorCode::INVALID_ARGUMENT, "Value must be non-negative");
}
// 处理数据
return ErrorCode::SUCCESS;
} catch (const AppException& e) {
errorMessage = e.what();
return e.getErrorCode();
} catch (const std::exception& e) {
errorMessage = e.what();
return ErrorCode::NETWORK_ERROR; // 默认为网络错误
}
}

// 只使用异常的函数
void processWithExceptions(int value) {
if (value < 0) {
throw AppException(ErrorCode::INVALID_ARGUMENT, "Value must be non-negative");
}
// 处理数据
}

异常处理的性能考虑

异常处理的性能开销

异常处理的性能开销主要来自:

  1. 异常表生成:编译器为每个函数生成异常表,增加代码大小
  2. 栈展开:当异常抛出时,需要沿着调用栈向上查找catch块
  3. 异常对象的创建和销毁:异常对象通常在堆上分配

性能优化策略

  1. 只在真正的异常情况下使用异常:避免将异常用于常规控制流
  2. 使用noexcept说明符:对于不会抛出异常的函数,使用noexcept说明符,这样编译器可以进行优化
  3. 优化异常对象:对于自定义异常类,实现移动构造函数和移动赋值运算符,减少异常对象的复制开销
  4. 使用小型异常对象:异常对象应该尽可能小,只包含必要的信息
  5. 避免在异常处理中执行复杂操作:异常处理代码应该简洁,只包含必要的错误处理逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 优化异常对象
class MyException : public std::exception {
private:
std::string message;

public:
explicit MyException(std::string msg) : message(std::move(msg)) {}

// 移动构造函数
MyException(MyException&& other) noexcept : message(std::move(other.message)) {}

// 移动赋值运算符
MyException& operator=(MyException&& other) noexcept {
if (this != &other) {
message = std::move(other.message);
}
return *this;
}

// 禁止复制
MyException(const MyException&) = delete;
MyException& operator=(const MyException&) = delete;

const char* what() const noexcept override {
return message.c_str();
}
};

异常处理的测试

测试异常抛出

使用测试框架测试函数是否在预期条件下抛出正确的异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdexcept>

// 被测试的函数
int divide(int a, int b) {
if (b == 0) {
throw std::invalid_argument("Division by zero");
}
return a / b;
}

// 测试代码(使用Google Test框架示例)
TEST(DivideTest, ThrowsOnZeroDivision) {
EXPECT_THROW(divide(10, 0), std::invalid_argument);
}

TEST(DivideTest, ReturnsCorrectResult) {
EXPECT_EQ(divide(10, 2), 5);
EXPECT_EQ(divide(7, 3), 2);
}

测试异常安全

测试函数在抛出异常时是否保持异常安全:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <vector>

// 被测试的函数(应该提供强异常安全)
void appendIfPositive(std::vector<int>& vec, int value) {
if (value > 0) {
vec.push_back(value);
}
}

// 测试代码
TEST(ExceptionSafetyTest, AppendIfPositiveIsStrongExceptionSafe) {
std::vector<int> original = {1, 2, 3};
std::vector<int> testVec = original;

// 测试正常情况
appendIfPositive(testVec, 4);
EXPECT_EQ(testVec, std::vector<int>({1, 2, 3, 4}));

// 测试边界情况
testVec = original;
appendIfPositive(testVec, 0);
EXPECT_EQ(testVec, original);

testVec = original;
appendIfPositive(testVec, -1);
EXPECT_EQ(testVec, original);
}

模拟异常

在测试中模拟异常,验证系统的恢复能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <stdexcept>
#include <memory>

// 模拟接口
class Database {
public:
virtual ~Database() = default;
virtual void save(const std::string& data) = 0;
};

// 真实实现
class RealDatabase : public Database {
public:
void save(const std::string& data) override {
// 真实的保存操作
}
};

// 模拟实现(用于测试)
class MockDatabase : public Database {
public:
MockDatabase(bool throwOnSave) : throwOnSave(throwOnSave) {}

void save(const std::string& data) override {
if (throwOnSave) {
throw std::runtime_error("Simulated database error");
}
}

private:
bool throwOnSave;
};

// 被测试的服务
class UserService {
private:
std::unique_ptr<Database> db;

public:
UserService(std::unique_ptr<Database> db) : db(std::move(db)) {}

bool createUser(const std::string& userData) {
try {
db->save(userData);
return true;
} catch (const std::exception& e) {
// 记录错误
return false;
}
}
};

// 测试代码
TEST(UserServiceTest, HandlesDatabaseError) {
auto mockDb = std::make_unique<MockDatabase>(true); // 模拟异常
UserService service(std::move(mockDb));

bool result = service.createUser("{\"name\": \"John\"}");
EXPECT_FALSE(result);
}

TEST(UserServiceTest, CreatesUserSuccessfully) {
auto mockDb = std::make_unique<MockDatabase>(false); // 不模拟异常
UserService service(std::move(mockDb));

bool result = service.createUser("{\"name\": \"John\"}");
EXPECT_TRUE(result);
}

C++标准库中的异常

标准异常层次结构

C++标准库提供了一套异常类层次结构,所有标准异常都继承自std::exception

  • std::exception:所有标准异常的基类
    • std::bad_alloc:内存分配失败
    • std::bad_cast:类型转换失败
    • std::bad_typeid:类型信息获取失败
    • std::bad_exception:异常处理过程中的异常
    • std::logic_error:逻辑错误(程序逻辑问题)
      • std::domain_error:参数不在有效范围内
      • std::invalid_argument:无效参数
      • std::length_error:长度超出有效范围
      • std::out_of_range:索引超出有效范围
    • std::runtime_error:运行时错误(运行时环境问题)
      • std::range_error:计算结果超出有效范围
      • std::overflow_error:算术上溢
      • std::underflow_error:算术下溢
      • std::system_error:系统错误(C++11)

标准异常的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <stdexcept>
#include <vector>

void processVector(const std::vector<int>& vec, size_t index) {
if (index >= vec.size()) {
throw std::out_of_range("Index out of range");
}
std::cout << "Value at index " << index << ": " << vec[index] << std::endl;
}

void calculate(int a, int b) {
if (b == 0) {
throw std::invalid_argument("Division by zero");
}
if (a > 1000000 || b > 1000000) {
throw std::domain_error("Arguments too large");
}
std::cout << "Result: " << a / b << std::endl;
}

int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};

try {
processVector(vec, 10);
} catch (const std::out_of_range& e) {
std::cout << "Out of range error: " << e.what() << std::endl;
}

try {
calculate(10, 0);
} catch (const std::invalid_argument& e) {
std::cout << "Invalid argument error: " << e.what() << std::endl;
}

try {
calculate(2000000, 1);
} catch (const std::domain_error& e) {
std::cout << "Domain error: " << e.what() << std::endl;
}

return 0;
}

实际项目中的异常处理策略

分层异常处理

在大型项目中,通常采用分层的异常处理策略:

  • 底层:抛出具体的、详细的异常
  • 中层:捕获底层异常,转换为更具语义的异常,添加上下文信息
  • 顶层:捕获所有异常,记录错误,执行恢复操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 底层:数据库层
class DatabaseException : public std::runtime_error {
public:
DatabaseException(const std::string& sql, const std::string& error)
: std::runtime_error("Database error: " + error + " (SQL: " + sql + ")") {}
};

// 中层:业务逻辑层
class OrderProcessingException : public std::runtime_error {
public:
OrderProcessingException(const std::string& orderId, const std::exception& cause)
: std::runtime_error("Failed to process order " + orderId + ". Cause: " + cause.what()) {}
};

// 顶层:应用层
void processOrder(const std::string& orderId) {
try {
// 处理订单
// ...
} catch (const OrderProcessingException& e) {
// 记录错误
logError(e.what());
// 通知用户
notifyUser("Order processing failed: " + e.what());
// 执行恢复操作
scheduleRetry(orderId);
} catch (const std::exception& e) {
// 记录未预期的错误
logError("Unexpected error: " + e.what());
// 通知管理员
notifyAdmin("Unexpected error: " + e.what());
// 执行紧急恢复操作
emergencyRecovery();
}
}

异常日志

良好的异常日志对于调试和监控至关重要。异常日志应该包含:

  • 异常类型和消息
  • 异常发生的位置(文件、行号、函数名)
  • 异常发生时的上下文信息(如用户ID、请求参数等)
  • 异常的调用栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdexcept>
#include <source_location>

void logException(const std::exception& e, const std::source_location& loc = std::source_location::current()) {
std::cerr << "Exception at " << loc.file_name() << ":" << loc.line()
<< " in " << loc.function_name() << std::endl;
std::cerr << "Error: " << e.what() << std::endl;
// 可以将日志写入文件或发送到日志系统
}

void processData(int value) {
try {
if (value < 0) {
throw std::invalid_argument("Value must be non-negative");
}
// 处理数据
} catch (const std::exception& e) {
logException(e);
throw;
}
}

异常处理与测试

在实际项目中,应该为异常处理编写专门的测试:

  1. 测试异常抛出:测试函数是否在预期条件下抛出正确的异常
  2. 测试异常安全:测试函数在抛出异常时是否保持异常安全
  3. 测试异常恢复:测试系统在遇到异常时是否能够正确恢复
  4. 测试异常传播:测试异常是否能够正确传播到适当的处理点

总结

异常处理是C++编程中的重要组成部分,它提供了一种结构化的方式来处理错误,使得错误处理代码与正常业务逻辑分离,提高了代码的可读性和可维护性。

通过本章的学习,你应该掌握:

  1. 异常处理的基本语法:抛出和捕获异常
  2. 异常安全保证:基本异常安全、强异常安全和无异常保证
  3. 异常传播:异常如何在调用栈中传播,栈展开过程
  4. 异常与RAII的结合:如何使用RAII确保异常安全
  5. 自定义异常类:如何创建和使用自定义异常类
  6. 异常处理的最佳实践:只在特殊情况下使用异常,捕获适当的异常类型,使用异常层次结构等
  7. 异常处理的性能考虑:异常处理的性能开销,性能优化策略
  8. 异常处理的测试:测试异常抛出,测试异常安全,模拟异常
  9. C++标准库中的异常:标准异常层次结构,标准异常的使用
  10. 实际项目中的异常处理策略:分层异常处理,异常日志,异常处理与测试

异常处理是一把双刃剑,它既可以使程序更加健壮和可靠,也可能导致性能下降和代码复杂性增加。在实际项目中,应该根据具体情况,合理使用异常处理机制,遵循最佳实践,以达到代码质量和性能的平衡。

通过不断学习和实践,你会逐渐掌握异常处理的精髓,成为一名更加优秀的C++程序员。