第26章 高级异常处理
高级异常层次结构设计
异常层次结构的设计原则
设计一个良好的异常层次结构是大型系统中异常处理的基础,它应该遵循以下原则:
- 单一职责原则:每个异常类应该只表示一种特定类型的错误
- 层次清晰:异常类应该按照错误的严重程度和类型进行层次化组织
- 信息丰富:异常类应该包含足够的信息,以便于调试和错误处理
- 可扩展性:异常层次结构应该易于扩展,以适应系统的演化
标准异常层次结构分析
C++标准库提供了一个异常层次结构,从std::exception基类派生:
std::exception - 所有标准异常的基类std::bad_alloc - 内存分配失败std::bad_cast - 类型转换失败std::bad_typeid - 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 - 下溢错误
自定义异常层次结构示例
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 69 70 71 72 73 74 75 76 77 78
| #include <stdexcept> #include <string>
class AppException : public std::exception { protected: std::string message_; int error_code_; std::string file_; int line_;
public: AppException(const std::string& msg, int code, const std::string& file, int line) : message_(msg), error_code_(code), file_(file), line_(line) {}
const char* what() const noexcept override { return message_.c_str(); }
int error_code() const noexcept { return error_code_; }
const std::string& file() const noexcept { return file_; }
int line() const noexcept { return line_; } };
class NetworkException : public AppException { public: enum NetworkError { CONNECTION_FAILED, TIMEOUT, PROTOCOL_ERROR, SERVER_ERROR };
NetworkException(const std::string& msg, NetworkError code, const std::string& file, int line) : AppException(msg, static_cast<int>(code), file, line) {} };
class DatabaseException : public AppException { public: enum DatabaseError { CONNECTION_ERROR, QUERY_ERROR, TRANSACTION_ERROR, CONSTRAINT_VIOLATION };
DatabaseException(const std::string& msg, DatabaseError code, const std::string& file, int line) : AppException(msg, static_cast<int>(code), file, line) {} };
class BusinessLogicException : public AppException { public: enum BusinessError { INVALID_OPERATION, PERMISSION_DENIED, RESOURCE_NOT_FOUND, VALIDATION_ERROR };
BusinessLogicException(const std::string& msg, BusinessError code, const std::string& file, int line) : AppException(msg, static_cast<int>(code), file, line) {} };
#define THROW_NETWORK_EXCEPTION(msg, code) throw NetworkException(msg, code, __FILE__, __LINE__) #define THROW_DATABASE_EXCEPTION(msg, code) throw DatabaseException(msg, code, __FILE__, __LINE__) #define THROW_BUSINESS_EXCEPTION(msg, code) throw BusinessLogicException(msg, code, __FILE__, __LINE__)
|
异常层次结构的最佳实践
- 使用继承而非组合:异常类应该通过继承来建立层次关系,而不是通过组合
- 提供丰富的上下文信息:异常应该包含足够的信息,如错误代码、文件名、行号等
- 保持异常类轻量:异常类不应该包含重量级的成员,因为异常抛出时会进行拷贝
- 支持异常链:在C++11及以上版本,可以使用
std::exception_ptr实现异常链 - 使用 noexcept 规范:异常类的方法应该使用
noexcept修饰,以避免异常处理过程中再次抛出异常
异常传播与处理策略
异常传播机制
异常从抛出点开始,沿着调用栈向上传播,直到被捕获或导致程序终止。在传播过程中,会发生以下事情:
- 栈展开:销毁局部对象,按照构造顺序的逆序
- 异常过滤:检查每个catch块是否能够处理当前异常
- 异常匹配:选择最合适的catch块来处理异常
异常处理策略
1. 就地处理
在异常发生的地方直接处理,适用于可以恢复的错误情况。
1 2 3 4 5 6 7
| try { } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; }
|
2. 转换处理
将底层异常转换为更有意义的高层异常,适用于分层架构。
1 2 3 4 5 6 7 8 9
| try { database_->executeQuery(query); } catch (const std::runtime_error& e) { throw BusinessLogicException("Database operation failed: " + std::string(e.what()), BusinessLogicException::DATABASE_ERROR, __FILE__, __LINE__); }
|
3. 重新抛出
捕获异常后,添加额外信息并重新抛出,适用于需要添加上下文信息的场景。
1 2 3 4 5 6 7 8
| try { } catch (const AppException& e) { std::string enhanced_message = "Operation failed: " + std::string(e.what()); throw AppException(enhanced_message, e.error_code(), __FILE__, __LINE__); }
|
4. 异常透明
允许异常传播到更高层次,由调用者决定如何处理,适用于无法在当前层次有效处理的情况。
1 2 3 4 5
| void processData(const std::string& data) { validateData(data); storeData(data); }
|
异常传播的最佳实践
- 明确异常边界:在系统的边界处捕获异常,如API接口、线程入口点等
- 保持异常语义:不要捕获异常后返回错误码,这会丢失异常的语义信息
- 避免空catch块:空catch块会吞噬异常,导致难以调试的问题
- 使用引用捕获:使用
const&捕获异常,避免不必要的拷贝 - 按层次捕获:先捕获具体的异常,再捕获通用的异常
多线程与异步环境中的异常处理
线程中的异常
在多线程环境中,异常处理需要特别注意,因为线程中的未捕获异常会导致整个程序终止。
线程异常处理策略
- 线程内捕获:在每个线程的入口函数中捕获所有异常
- 异常传递:使用
std::exception_ptr在线程间传递异常 - 线程安全的异常处理:确保异常处理过程是线程安全的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <iostream> #include <thread> #include <exception> #include <stdexcept>
void threadFunction() { try { throw std::runtime_error("Thread exception"); } catch (const std::exception& e) { std::cerr << "Thread caught exception: " << e.what() << std::endl; } }
int main() { std::thread t(threadFunction); t.join(); return 0; }
|
异常在异步操作中的传递
在使用std::async、std::future等异步操作时,异常会被存储在std::future中,当调用get()时会重新抛出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <iostream> #include <future> #include <stdexcept>
int calculate() { throw std::runtime_error("Calculation error"); return 42; }
int main() { std::future<int> f = std::async(std::launch::async, calculate); try { int result = f.get(); std::cout << "Result: " << result << std::endl; } catch (const std::exception& e) { std::cerr << "Main caught exception: " << 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 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 69 70 71 72 73 74 75
| #include <iostream> #include <vector> #include <queue> #include <thread> #include <mutex> #include <condition_variable> #include <functional> #include <future> #include <stdexcept>
class ThreadPool { public: ThreadPool(size_t threads) : stop(false) { for(size_t i = 0; i < threads; ++i) { workers.emplace_back([this] { while(true) { std::function<void()> task; { std::unique_lock<std::mutex> lock(this->queue_mutex); this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); }); if(this->stop && this->tasks.empty()) return; task = std::move(this->tasks.front()); this->tasks.pop(); } try { task(); } catch (const std::exception& e) { std::cerr << "ThreadPool caught exception: " << e.what() << std::endl; } } }); } }
template<class F, class... Args> auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> { using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>( std::bind(std::forward<F>(f), std::forward<Args>(args)...) );
std::future<return_type> res = task->get_future(); { std::unique_lock<std::mutex> lock(queue_mutex); if(stop) throw std::runtime_error("enqueue on stopped ThreadPool"); tasks.emplace([task]() { (*task)(); }); } condition.notify_one(); return res; }
~ThreadPool() { { std::unique_lock<std::mutex> lock(queue_mutex); stop = true; } condition.notify_all(); for(std::thread &worker: workers) worker.join(); }
private: std::vector<std::thread> workers; std::queue<std::function<void()>> tasks; std::mutex queue_mutex; std::condition_variable condition; bool stop; };
|
多线程异常处理的最佳实践
- 每个线程都要有异常处理:确保每个线程的入口函数都有try-catch块
- 使用std::exception_ptr传递异常:在需要在线程间传递异常时使用
- 线程池中的异常隔离:确保一个任务的异常不会影响其他任务
- 异步操作的异常处理:在调用future.get()时捕获异常
- 避免在析构函数中抛出异常:析构函数应该是noexcept的
高级异常安全模式
异常安全级别
异常安全分为三个级别:
- 基本保证:如果异常被抛出,程序不会泄漏资源,对象仍然处于有效状态,但可能不是原始状态
- 强保证:如果异常被抛出,程序状态保持不变(原子操作)
- 无抛出保证:操作绝不会抛出异常
实现强异常安全的技术
1. 拷贝-交换 idiom
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 <algorithm> #include <vector>
class ResourceManager { private: std::vector<int>* data;
public: ResourceManager(size_t size) : data(new std::vector<int>(size)) {}
ResourceManager(const ResourceManager& other) : data(new std::vector<int>(*other.data)) {}
~ResourceManager() { delete data; }
ResourceManager& operator=(ResourceManager other) { swap(*this, other); return *this; }
friend void swap(ResourceManager& a, ResourceManager& b) noexcept { std::swap(a.data, b.data); }
void resize(size_t newSize) { ResourceManager temp(newSize); std::copy(data->begin(), data->begin() + std::min(data->size(), newSize), temp.data->begin()); swap(*this, temp); } };
|
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 28 29 30 31 32 33 34 35 36 37 38 39 40
| class Transaction { private: bool committed = false;
public: void begin() { }
void commit() { committed = true; }
void rollback() { }
~Transaction() { if (!committed) { rollback(); } } };
void performOperation() { Transaction tx; tx.begin();
try { tx.commit(); } catch (...) { throw; } }
|
异常中立性
异常中立性是指函数应该将异常传递给调用者,而不是捕获并处理它们,除非函数有特定的责任来处理这些异常。
1 2 3 4 5 6 7 8
| template<typename Iterator, typename Func> void for_each(Iterator begin, Iterator end, Func f) { for (; begin != end; ++begin) { f(*begin); } }
|
异常安全的最佳实践
- 使用RAII管理资源:利用析构函数自动释放资源,确保即使在异常情况下也不会泄漏
- 优先使用标准容器和智能指针:它们已经实现了异常安全
- 使用拷贝-交换 idiom:实现强异常安全的赋值运算符
- 保持操作原子性:将复合操作分解为多个步骤,确保中间步骤的异常不会破坏状态
- 正确使用noexcept:对于不会抛出异常的操作使用noexcept
现代C++中的异常处理
C++11及以上的异常处理增强
1. std::exception_ptr
用于在不同上下文中传递异常。
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
| #include <iostream> #include <exception> #include <stdexcept>
void handle_exception(std::exception_ptr ep) { try { if (ep) { std::rethrow_exception(ep); } } catch (const std::exception& e) { std::cerr << "Caught exception: " << e.what() << std::endl; } }
int main() { std::exception_ptr ep; try { throw std::runtime_error("Test exception"); } catch (...) { ep = std::current_exception(); } handle_exception(ep); return 0; }
|
2. noexcept 规范
指定函数是否可能抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12
| void swap(int& a, int& b) noexcept { int temp = a; a = b; b = temp; }
template<typename T> void swap(T& a, T& b) noexcept(noexcept(std::swap(a, b))) { std::swap(a, b); }
|
3. 异常与移动语义
移动构造函数和移动赋值运算符应该是noexcept的,以确保在STL容器中的使用安全。
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
| class MyType { private: int* data;
public: MyType() : data(nullptr) {} MyType(MyType&& other) noexcept : data(other.data) { other.data = nullptr; } MyType& operator=(MyType&& other) noexcept { if (this != &other) { delete data; data = other.data; other.data = nullptr; } return *this; } ~MyType() { delete data; } };
|
C++20中的异常处理
1. 概念与异常
使用概念可以在编译时检查函数是否可能抛出异常。
1 2 3 4 5 6 7 8
| #include <concepts>
template<typename F> requires std::invocable<F> && noexcept(std::invoke(std::declval<F>())) void safe_invoke(F&& f) { std::invoke(std::forward<F>(f)); }
|
2. 协程中的异常处理
协程中的异常会被存储在协程的promise对象中,当协程被恢复时重新抛出。
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 <coroutine> #include <exception> #include <stdexcept> #include <iostream>
struct Task { struct promise_type { std::exception_ptr exception; Task get_return_object() { return Task{std::coroutine_handle<promise_type>::from_promise(*this)}; } std::suspend_never initial_suspend() noexcept { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void unhandled_exception() { exception = std::current_exception(); } void return_void() {} }; std::coroutine_handle<promise_type> handle; ~Task() { if (handle) handle.destroy(); } void resume() { handle.resume(); if (handle.promise().exception) { std::rethrow_exception(handle.promise().exception); } } };
Task foo() { throw std::runtime_error("Coroutine exception"); co_return; }
int main() { try { auto task = foo(); task.resume(); } catch (const std::exception& e) { std::cerr << "Caught exception: " << e.what() << std::endl; } return 0; }
|
现代C++异常处理的最佳实践
- 使用std::exception_ptr传递异常:在需要在不同上下文中传递异常时使用
- 正确使用noexcept:对于不会抛出异常的操作使用noexcept,特别是移动操作
- 利用移动语义:移动构造函数和移动赋值运算符应该是noexcept的
- 结合RAII和异常:使用RAII确保资源在异常情况下的正确释放
- 考虑异常的性能影响:在性能关键路径上,考虑使用错误码而非异常
异常处理的性能优化
异常的性能开销
异常处理的性能开销主要来自以下几个方面:
- 异常表生成:编译器需要为可能抛出异常的函数生成异常表
- 栈展开:当异常被抛出时,需要展开栈,销毁局部对象
- 异常捕获:查找合适的catch块需要时间
- 异常对象的创建和销毁:异常对象需要在堆上分配内存
性能优化策略
1. 减少异常的使用
在性能关键路径上,考虑使用错误码而非异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| bool parseFast(const std::string& input, int& result) { if () { return false; } result = ; return true; }
int parse(const std::string& input) { int result; if (!parseFast(input, result)) { throw std::invalid_argument("Invalid input: " + input); } return result; }
|
2. 优化异常对象
异常对象应该轻量,避免包含重量级的成员。
1 2 3 4 5 6 7 8 9 10 11 12
| class LightweightException : public std::exception { private: const char* message_;
public: explicit LightweightException(const char* msg) : message_(msg) {} const char* what() const noexcept override { return message_; } };
|
3. 使用noexcept减少异常表大小
对于不会抛出异常的函数,使用noexcept可以减少生成的异常表大小。
1 2 3 4 5 6
| void swap(int& a, int& b) noexcept { int temp = a; a = b; b = temp; }
|
4. 异常预测
在某些编译器中,可以使用__builtin_expect或类似的扩展来提示编译器异常抛出的可能性。
1 2 3 4 5 6
| void process(int value) { if (__builtin_expect(value < 0, 0)) { throw std::invalid_argument("Negative value"); } }
|
异常处理的性能基准测试
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> #include <chrono> #include <stdexcept>
int divideWithException(int a, int b) { if (b == 0) { throw std::runtime_error("Division by zero"); } return a / b; }
bool divideWithErrorCode(int a, int b, int& result) { if (b == 0) { return false; } result = a / b; return true; }
int main() { const int iterations = 10000000; int result; auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < iterations; ++i) { try { result = divideWithException(100, i + 1); } catch (...) { } } auto end = std::chrono::high_resolution_clock::now(); std::cout << "Exception version (no exception): " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << " ms" << std::endl; start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < iterations; ++i) { divideWithErrorCode(100, i + 1, result); } end = std::chrono::high_resolution_clock::now(); std::cout << "Error code version: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << " ms" << std::endl; return 0; }
|
性能优化的最佳实践
- 在性能关键路径上使用错误码:对于频繁调用的函数,考虑使用错误码而非异常
- 保持异常对象轻量:异常对象应该小而简单
- 使用noexcept:对于不会抛出异常的函数使用noexcept
- 避免在循环中抛出异常:异常应该用于异常情况,而不是正常的控制流
- 合理使用异常层次结构:避免过深的异常层次结构
异常处理的测试策略
异常测试的重要性
异常处理代码和正常代码一样需要测试,以确保:
- 异常被正确抛出:在预期的情况下抛出正确的异常
- 异常被正确捕获:异常能够被适当的catch块捕获
- 异常安全保证:在异常情况下,程序状态保持一致,资源不泄漏
- 异常处理路径的正确性:异常处理代码能够正确执行
异常测试技术
1. 使用断言测试异常
1 2 3 4 5 6 7 8 9 10 11 12
| #include <cassert> #include <stdexcept>
void testDivideByZero() { bool threw = false; try { int result = 10 / 0; } catch (const std::exception&) { threw = true; } assert(threw); }
|
2. 使用测试框架
许多测试框架都提供了专门的宏来测试异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <gtest/gtest.h>
TEST(ExceptionTest, DivideByZero) { EXPECT_THROW({ int result = 10 / 0; }, std::exception); }
TEST(ExceptionTest, InvalidArgument) { EXPECT_THROW({ throw std::invalid_argument("Test"); }, std::invalid_argument); }
|
3. 测试异常安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <vector> #include <stdexcept>
void testExceptionSafety() { std::vector<int> vec = {1, 2, 3, 4, 5}; size_t originalSize = vec.size(); try { vec.insert(vec.end(), 1000000000, 0); } catch (const std::exception&) { ASSERT_EQ(vec.size(), originalSize); ASSERT_EQ(vec, std::vector<int>({1, 2, 3, 4, 5})); } }
|
异常测试的最佳实践
- 测试所有异常路径:确保每个可能抛出异常的路径都被测试
- 测试异常类型:确保抛出的异常类型正确
- 测试异常信息:确保异常包含正确的错误信息
- 测试异常安全:确保在异常情况下程序状态保持一致
- 测试异常传播:确保异常能够正确传播到预期的处理点
- 使用专门的测试框架:利用测试框架提供的异常测试功能
实际案例分析与最佳实践
案例1:网络服务中的异常处理
问题描述
设计一个网络服务,需要处理各种网络错误、数据库错误和业务逻辑错误。
解决方案
设计异常层次结构:
NetworkException - 网络相关错误DatabaseException - 数据库相关错误BusinessLogicException - 业务逻辑错误
异常处理策略:
- 在底层函数中抛出具体异常
- 在服务接口层捕获异常,转换为适当的HTTP状态码
- 记录异常详细信息用于调试
实现示例:
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 69 70 71 72 73 74 75 76 77 78 79 80
| #include <string> #include <stdexcept>
class ServiceException : public std::exception { protected: std::string message_; int error_code_;
public: ServiceException(const std::string& msg, int code) : message_(msg), error_code_(code) {}
const char* what() const noexcept override { return message_.c_str(); }
int error_code() const noexcept { return error_code_; } };
class NetworkException : public ServiceException { public: NetworkException(const std::string& msg, int code) : ServiceException(msg, code) {} };
class DatabaseException : public ServiceException { public: DatabaseException(const std::string& msg, int code) : ServiceException(msg, code) {} };
class BusinessLogicException : public ServiceException { public: BusinessLogicException(const std::string& msg, int code) : ServiceException(msg, code) {} };
class UserService { public: User getUserById(int id) { try { if (id <= 0) { throw BusinessLogicException("Invalid user ID", 400); }
return database_->getUser(id); } catch (const DatabaseException& e) { logger_->error("Database error: {}", e.what()); throw ServiceException("Database error", 500); } catch (const BusinessLogicException&) { throw; } } };
void handleGetUserRequest(int user_id, HttpResponse& response) { try { User user = userService_->getUserById(user_id); response.setStatus(200); response.setBody(user.toJson()); } catch (const ServiceException& e) { response.setStatus(e.error_code()); response.setBody("{\"error\": \"" + std::string(e.what()) + "\"}"); } catch (const std::exception& e) { response.setStatus(500); response.setBody("{\"error\": \"Internal server error\"}"); logger_->error("Unexpected error: {}", e.what()); } }
|
案例2:高性能计算中的异常处理
问题描述
设计一个高性能计算库,需要在保证性能的同时处理可能的错误情况。
解决方案
分层异常处理:
- 内部计算函数使用错误码,避免异常开销
- 公共API使用异常,提供更好的用户体验
性能优化:
- 在性能关键路径上使用错误码
- 异常对象轻量设计
- 使用noexcept减少异常表大小
实现示例:
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
| #include <stdexcept> #include <vector>
bool computeInternal(const std::vector<double>& input, std::vector<double>& output, std::string& error_message) { if (input.empty()) { error_message = "Input vector is empty"; return false; }
output.resize(input.size()); for (size_t i = 0; i < input.size(); ++i) { output[i] = input[i] * 2.0; }
return true; }
std::vector<double> compute(const std::vector<double>& input) { std::vector<double> output; std::string error_message;
if (!computeInternal(input, output, error_message)) { throw std::invalid_argument(error_message); }
return output; }
void computeFast(const std::vector<double>& input, std::vector<double>& output) noexcept { output.resize(input.size()); for (size_t i = 0; i < input.size(); ++i) { output[i] = input[i] * 2.0; } }
|
异常处理的最佳实践总结
设计合理的异常层次结构:
- 从std::exception派生
- 按照错误类型和严重程度分层
- 包含足够的错误信息
选择合适的异常处理策略:
- 就地处理:对于可以恢复的错误
- 转换处理:对于分层架构中的异常
- 重新抛出:对于需要添加上下文信息的异常
- 异常透明:对于无法在当前层次处理的异常
确保异常安全:
- 使用RAII管理资源
- 实现强异常安全(拷贝-交换idiom)
- 正确使用noexcept
考虑性能影响:
- 在性能关键路径上使用错误码
- 保持异常对象轻量
- 避免在循环中抛出异常
测试异常处理:
- 测试所有异常路径
- 测试异常类型和信息
- 测试异常安全保证
多线程环境中的异常处理:
- 每个线程都要有异常处理
- 使用std::exception_ptr传递异常
- 线程池中的异常隔离
现代C++特性的应用:
- 使用std::exception_ptr传递异常
- 正确使用noexcept
- 移动构造函数和移动赋值运算符应该是noexcept的
- 结合RAII和异常
通过遵循这些最佳实践,可以构建更加健壮、可靠、高性能的C++应用程序,同时保持代码的清晰性和可维护性。