1. 异常安全与RAII模式的核心概念
在C++开发中,异常安全性和资源管理是两个经常让开发者头疼的问题。异常安全指的是当程序抛出异常时,代码能够保持数据一致性和资源不泄漏的状态。而RAII(Resource Acquisition Is Initialization)则是C++特有的资源管理范式,通过对象的生命周期来管理资源。
我经历过一个典型的资源泄漏场景:在多线程环境下,一个文件处理类在构造函数中打开文件,在析构函数中关闭文件。但当文件打开后如果抛出异常,已经分配的系统文件句柄就会泄漏。这就是典型的异常安全问题。
RAII模式的核心思想其实很简单:将资源获取与对象初始化绑定,资源释放与对象析构绑定。这种模式之所以在C++中如此重要,是因为:
- C++没有像Java那样的finally块
- 手动资源管理在异常场景下极易出错
- 栈展开(stack unwinding)机制会保证局部对象的析构
2. 异常安全级别的深入解析
C++社区通常将异常安全分为三个级别:
2.1 基本保证(Basic Guarantee)
这是最低要求,确保:
- 不会资源泄漏
- 所有对象处于有效状态(尽管内容可能改变)
- 程序保持一致性
实现要点:
- 所有资源都由RAII对象管理
- 修改数据前先创建副本
- 使用swap技巧实现原子性修改
cpp复制class BasicSafe {
std::vector<int> data;
public:
void modify(size_t index, int value) {
auto temp = data; // 先复制
temp[index] = value; // 修改副本
data.swap(temp); // 原子交换
}
};
2.2 强保证(Strong Guarantee)
比基本保证更严格,要求:
- 操作要么完全成功
- 要么完全回滚到操作前状态
- 事务语义(transactional)
实现模式:
- Copy-and-swap惯用法
- 非变异操作优先
- 将可能失败的操作前置
cpp复制class StrongSafe {
std::unique_ptr<int[]> data;
size_t size;
public:
void resize(size_t new_size) {
auto temp = std::make_unique<int[]>(new_size);
std::copy(data.get(), data.get()+std::min(size,new_size), temp.get());
data.swap(temp); // 不会抛出异常
size = new_size;
}
};
2.3 不抛保证(Nothrow Guarantee)
最高级别的保证,承诺:
- 操作绝不会抛出异常
- 常用于移动操作和析构函数
实现技巧:
- 使用noexcept修饰符
- 避免动态内存分配
- 只调用不抛异常的操作
cpp复制class NothrowSafe {
int buffer[100]; // 固定大小数组
public:
void swap(NothrowSafe& other) noexcept {
std::swap_ranges(buffer, buffer+100, other.buffer);
}
};
3. RAII模式的高级应用技巧
3.1 自定义资源管理类
标准的智能指针(unique_ptr, shared_ptr)已经覆盖了大部分内存管理场景,但对于其他资源类型,我们需要自定义RAII包装器。
一个数据库连接类的典型实现:
cpp复制class DBConnection {
sqlite3* conn;
public:
explicit DBConnection(const char* filename) {
if(sqlite3_open(filename, &conn) != SQLITE_OK) {
throw std::runtime_error("Failed to open database");
}
}
~DBConnection() noexcept {
if(conn) sqlite3_close(conn);
}
// 禁用拷贝
DBConnection(const DBConnection&) = delete;
DBConnection& operator=(const DBConnection&) = delete;
// 允许移动
DBConnection(DBConnection&& other) noexcept : conn(other.conn) {
other.conn = nullptr;
}
DBConnection& operator=(DBConnection&& other) noexcept {
if(this != &other) {
if(conn) sqlite3_close(conn);
conn = other.conn;
other.conn = nullptr;
}
return *this;
}
};
关键设计点:
- 构造函数获取资源,失败时抛出异常
- 析构函数释放资源,标记为noexcept
- 禁用拷贝构造和拷贝赋值
- 实现移动语义,同样保证不抛异常
3.2 多资源管理策略
当类需要管理多个独立资源时,可以采用嵌套RAII模式:
cpp复制class MultiResource {
std::unique_ptr<NetworkConnection> net;
std::unique_ptr<FileHandler> file;
std::lock_guard<std::mutex> lock;
public:
MultiResource(NetworkConnection* n, FileHandler* f, std::mutex& m)
: net(n), file(f), lock(m)
{
if(!net->isValid() || !file->isOpen()) {
throw std::runtime_error("Resource invalid");
}
}
// 自动生成的析构函数会按相反顺序释放资源
};
资源释放顺序:
- lock_guard析构(释放互斥锁)
- unique_ptr
析构 - unique_ptr
析构
这个顺序与构造顺序完全相反,是C++标准明确规定的行为。
4. 异常安全与并发编程
在多线程环境下,异常安全变得更加复杂。考虑以下场景:
cpp复制class ThreadSafeQueue {
std::queue<int> data;
mutable std::mutex mtx;
public:
void push(int value) {
std::lock_guard<std::mutex> lock(mtx);
data.push(value); // 可能抛出bad_alloc
}
bool try_pop(int& value) noexcept {
std::lock_guard<std::mutex> lock(mtx);
if(data.empty()) return false;
value = data.front();
data.pop(); // 不抛异常
return true;
}
};
关键观察:
- push操作提供基本保证:如果抛出异常,队列状态不变(因为操作在拷贝完成后才修改)
- try_pop提供不抛保证:所有操作都不会抛出异常
- mutex由lock_guard管理,确保异常安全
更复杂的例子:事务性批量操作
cpp复制class BatchProcessor {
std::vector<int> data;
std::mutex mtx;
public:
void batch_insert(std::vector<int>&& new_items) {
std::vector<int> temp;
{
std::lock_guard<std::mutex> lock(mtx);
temp = data; // 复制当前数据
}
// 在副本上操作(可能耗时)
temp.insert(temp.end(), new_items.begin(), new_items.end());
// 原子性提交
std::lock_guard<std::mutex> lock(mtx);
data.swap(temp); // 不抛异常
}
};
这种模式:
- 减少了持锁时间
- 提供了强异常保证
- 允许在非锁定状态下进行复杂计算
5. 常见陷阱与最佳实践
5.1 构造函数中的异常
构造函数是异常安全的关键点,因为:
- 构造函数没有返回值
- 构造失败时只能抛出异常
- 部分构造的对象需要正确清理
错误示例:
cpp复制class Problematic {
int* ptr1;
int* ptr2;
public:
Problematic() {
ptr1 = new int(42);
ptr2 = new int(84); // 如果这里抛出异常...
}
~Problematic() {
delete ptr1;
delete ptr2;
}
};
修正方案:
cpp复制class SafeConstruct {
std::unique_ptr<int> ptr1;
std::unique_ptr<int> ptr2;
public:
SafeConstruct()
: ptr1(std::make_unique<int>(42)),
ptr2(std::make_unique<int>(84))
{}
// 不需要显式析构函数
};
5.2 虚函数与析构
当涉及继承时,需要注意:
- 基类析构函数应该为virtual(如果有多态需求)
- 派生类析构函数自动为virtual
- 析构函数应该标记为noexcept
cpp复制class Base {
public:
virtual ~Base() noexcept = default;
};
class Derived : public Base {
std::unique_ptr<Resource> res;
public:
~Derived() noexcept override = default;
// 自动调用res的析构函数
};
5.3 移动语义的异常安全
移动操作通常应该标记为noexcept:
- 标准库容器会优先使用不抛异常的移动操作
- 移动后源对象处于有效但未指定状态
cpp复制class Movable {
std::vector<int> data;
public:
Movable(Movable&& other) noexcept
: data(std::move(other.data)) {}
Movable& operator=(Movable&& other) noexcept {
if(this != &other) {
data = std::move(other.data);
}
return *this;
}
};
5.4 资源所有权转移
有时候我们需要在RAII对象之间转移资源所有权:
cpp复制class FileWrapper {
FILE* file;
public:
explicit FileWrapper(const char* filename)
: file(fopen(filename, "r")) {
if(!file) throw std::runtime_error("Open failed");
}
~FileWrapper() { if(file) fclose(file); }
// 释放所有权
FILE* release() noexcept {
FILE* temp = file;
file = nullptr;
return temp;
}
// 获取原始指针(不转移所有权)
FILE* get() const noexcept { return file; }
};
使用场景:
cpp复制void process_file() {
FileWrapper fw("data.txt");
// 临时将所有权转移给C接口
FILE* raw_file = fw.release();
legacy_c_function(raw_file);
// C函数负责关闭文件
// 此时fw不再拥有资源
}
6. 现代C++中的新特性应用
6.1 使用std::optional处理可能失败的操作
cpp复制std::optional<int> safe_divide(int a, int b) {
if(b == 0) return std::nullopt;
return a / b;
}
void example() {
if(auto result = safe_divide(10, 2)) {
std::cout << *result << std::endl;
} else {
std::cout << "Division failed" << std::endl;
}
}
优势:
- 不使用异常机制
- 明确表达可能失败的操作
- 调用方必须显式处理失败情况
6.2 std::variant作为类型安全的union
cpp复制class ResourceHolder {
std::variant<std::monostate, File, Socket, Memory> resource;
public:
void open_file(const std::string& path) {
resource.emplace<File>(path);
}
void connect_socket(const std::string& host) {
resource.emplace<Socket>(host);
}
~ResourceHolder() {
std::visit([](auto& res) {
using T = std::decay_t<decltype(res)>;
if constexpr (!std::is_same_v<T, std::monostate>) {
res.close();
}
}, resource);
}
};
特点:
- 类型安全的资源存储
- 析构时自动调用正确的关闭方法
- 不需要虚函数或动态多态
6.3 std::expected提案(C++23)
未来更优雅的错误处理方式:
cpp复制std::expected<int, std::error_code> safe_operation() {
if(failure_condition) {
return std::unexpected(make_error_code(std::errc::invalid_argument));
}
return 42;
}
void example() {
auto result = safe_operation();
if(result) {
use(*result);
} else {
handle_error(result.error());
}
}
与异常相比的优势:
- 明确标记可能失败的函数
- 错误处理是显式的
- 性能接近返回码方式
7. 性能考量与优化技巧
7.1 异常处理的成本
异常机制的主要开销来自:
- 正常执行路径的零成本(现代编译器)
- 抛出异常时的高成本(栈展开、类型匹配)
- 代码体积增加(异常处理表)
优化建议:
- 不要将异常用于常规控制流
- 在性能关键路径避免可能抛出异常的操作
- 使用noexcept标记不会抛出异常的函数
7.2 小对象优化
对于频繁创建销毁的小型RAII对象:
cpp复制class SmallObject {
union {
int value;
std::aligned_storage_t<sizeof(int), alignof(int)> buffer;
};
bool owns_resource;
public:
explicit SmallObject(int v) : value(v), owns_resource(true) {}
~SmallObject() {
if(owns_resource) cleanup(value);
}
// 移动构造
SmallObject(SmallObject&& other) noexcept
: value(other.value), owns_resource(other.owns_resource) {
other.owns_resource = false;
}
};
特点:
- 避免堆分配
- 移动操作极其高效
- 适合高频使用的小型资源
7.3 延迟初始化模式
对于构造成本高的资源:
cpp复制class LazyResource {
mutable std::once_flag init_flag;
mutable std::unique_ptr<Expensive> resource;
void init_resource() const {
resource = std::make_unique<Expensive>();
}
public:
void use() const {
std::call_once(init_flag, &LazyResource::init_resource, this);
resource->do_something();
}
};
优势:
- 构造时不立即分配资源
- 线程安全的延迟初始化
- 自动管理资源生命周期
8. 测试异常安全性的策略
8.1 注入异常测试
使用特殊类在指定点抛出异常:
cpp复制struct ThrowOnNth {
int& counter;
int throw_at;
void check() {
if(++counter == throw_at) {
throw std::runtime_error("Injected failure");
}
}
};
void test_exception_safety() {
for(int i=1; ; ++i) {
int counter = 0;
try {
ThrowOnNth ton{counter, i};
operation_under_test(ton);
break; // 所有点都测试通过
} catch(...) {
// 验证状态是否一致
assert(check_invariants());
}
}
}
8.2 验证不变量
定义类不变量检查方法:
cpp复制class Account {
int balance;
// ...
public:
bool validate() const {
return balance >= 0; // 余额不能为负
}
};
void transaction(Account& a, Account& b, int amount) {
Account a_old = a;
Account b_old = b;
try {
a.withdraw(amount);
b.deposit(amount);
} catch(...) {
assert(a.validate() && b.validate());
assert(a == a_old && b == b_old); // 强保证
throw;
}
}
8.3 使用自定义分配器测试内存不足
cpp复制template<typename T>
class FaultInjectionAllocator {
static int alloc_count;
static int fail_after;
public:
using value_type = T;
template<typename U>
struct rebind { using other = FaultInjectionAllocator<U>; };
T* allocate(std::size_t n) {
if(fail_after >= 0 && ++alloc_count > fail_after) {
throw std::bad_alloc();
}
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, std::size_t) { ::operator delete(p); }
static void set_fail_after(int count) { fail_after = count; }
static void reset() { alloc_count = 0; fail_after = -1; }
};
template<typename T> int FaultInjectionAllocator<T>::alloc_count = 0;
template<typename T> int FaultInjectionAllocator<T>::fail_after = -1;
void test_with_memory_failures() {
using MyVector = std::vector<int, FaultInjectionAllocator<int>>;
for(int i=0; ; ++i) {
FaultInjectionAllocator<int>::reset();
FaultInjectionAllocator<int>::set_fail_after(i);
try {
MyVector v;
v.reserve(100); // 可能抛出bad_alloc
break;
} catch(const std::bad_alloc&) {
// 验证没有资源泄漏
}
}
}
9. 跨语言边界的异常处理
9.1 C++异常与C接口
在C回调中捕获C++异常的危险示例:
cpp复制// 错误方式
extern "C" void c_callback() {
try {
cpp_function_that_may_throw();
} catch(...) {
// 违反C ABI
}
}
正确做法:
cpp复制extern "C" int c_callback() noexcept {
try {
cpp_function_that_may_throw();
return 0; // 成功
} catch(const std::exception& e) {
log_error(e.what());
return -1; // 错误码
} catch(...) {
log_error("Unknown exception");
return -2;
}
}
9.2 与Python等脚本语言交互
使用pybind11时的异常转换:
cpp复制PYBIND11_MODULE(example, m) {
py::register_exception<MyException>(m, "MyPyException");
m.def("risky_function", []() {
try {
return may_throw();
} catch(const MyException& e) {
throw py::value_error(e.what());
}
});
}
关键点:
- 将C++异常转换为Python异常
- 确保资源在转换过程中不被泄漏
- 异常类型映射要一致
9.3 COM接口的异常处理
在Windows COM组件中:
cpp复制STDMETHODIMP MyComClass::Method() noexcept {
try {
cpp_method_that_may_throw();
return S_OK;
} catch(const std::exception& e) {
return Error(E_FAIL, e.what());
} catch(...) {
return E_UNEXPECTED;
}
}
COM规范要求:
- 所有接口方法标记为noexcept
- 使用HRESULT返回错误
- 内部捕获所有C++异常
10. 实际工程案例解析
10.1 数据库事务管理器
cpp复制class Transaction {
DBConnection& conn;
bool committed = false;
public:
explicit Transaction(DBConnection& c) : conn(c) {
conn.execute("BEGIN TRANSACTION");
}
~Transaction() noexcept {
if(!committed) {
try {
conn.execute("ROLLBACK");
} catch(...) {
// 记录日志,但不能抛出
}
}
}
void commit() {
conn.execute("COMMIT");
committed = true;
}
void execute(const std::string& sql) {
conn.execute(sql);
}
};
使用模式:
cpp复制void transfer_funds(DBConnection& db, int from, int to, int amount) {
Transaction trans(db);
try {
trans.execute("UPDATE accounts SET balance = balance - " +
std::to_string(amount) + " WHERE id = " + std::to_string(from));
trans.execute("UPDATE accounts SET balance = balance + " +
std::to_string(amount) + " WHERE id = " + std::to_string(to));
trans.commit();
} catch(...) {
// 事务自动回滚
throw;
}
}
10.2 网络连接池实现
cpp复制class ConnectionPool {
std::mutex mtx;
std::vector<std::unique_ptr<Connection>> pool;
std::condition_variable cv;
struct PooledConnection {
ConnectionPool* pool;
std::unique_ptr<Connection> conn;
explicit PooledConnection(ConnectionPool* p) : pool(p) {
std::unique_lock<std::mutex> lock(pool->mtx);
pool->cv.wait(lock, [p] { return !p->pool.empty(); });
conn = std::move(pool->pool.back());
pool->pool.pop_back();
}
~PooledConnection() {
std::lock_guard<std::mutex> lock(pool->mtx);
pool->pool.push_back(std::move(conn));
pool->cv.notify_one();
}
Connection* operator->() { return conn.get(); }
};
public:
explicit ConnectionPool(size_t size) {
for(size_t i=0; i<size; ++i) {
pool.push_back(std::make_unique<Connection>());
}
}
PooledConnection get_connection() {
return PooledConnection(this);
}
};
关键特性:
- 连接获取和返还是异常安全的
- 使用RAII管理连接生命周期
- 线程安全且高效
10.3 图形渲染资源管理
cpp复制class GLBuffer {
GLuint id;
static GLuint create_buffer() {
GLuint buf;
glGenBuffers(1, &buf);
if(buf == 0) throw std::runtime_error("Failed to create buffer");
return buf;
}
static void delete_buffer(GLuint buf) noexcept {
if(buf) glDeleteBuffers(1, &buf);
}
public:
GLBuffer() : id(create_buffer()) {}
~GLBuffer() noexcept { delete_buffer(id); }
// 移动语义
GLBuffer(GLBuffer&& other) noexcept : id(other.id) { other.id = 0; }
GLBuffer& operator=(GLBuffer&& other) noexcept {
if(this != &other) {
delete_buffer(id);
id = other.id;
other.id = 0;
}
return *this;
}
// 禁用拷贝
GLBuffer(const GLBuffer&) = delete;
GLBuffer& operator=(const GLBuffer&) = delete;
void bind(GLenum target) const noexcept {
glBindBuffer(target, id);
}
};
设计要点:
- 封装OpenGL原生资源
- 确保资源释放
- 支持移动语义
- 禁用拷贝(OpenGL资源通常不可拷贝)
11. 模板元编程中的RAII应用
11.1 类型安全的资源句柄
cpp复制template<typename Traits>
class ResourceHandle {
using HandleType = typename Traits::HandleType;
HandleType handle;
public:
explicit ResourceHandle(HandleType h = Traits::null())
: handle(h) {}
~ResourceHandle() {
if(Traits::isValid(handle)) {
Traits::release(handle);
}
}
// 移动语义
ResourceHandle(ResourceHandle&& other) noexcept
: handle(other.handle) {
other.handle = Traits::null();
}
ResourceHandle& operator=(ResourceHandle&& other) noexcept {
if(this != &other) {
reset();
handle = other.handle;
other.handle = Traits::null();
}
return *this;
}
void reset(HandleType new_handle = Traits::null()) {
if(Traits::isValid(handle)) {
Traits::release(handle);
}
handle = new_handle;
}
HandleType get() const noexcept { return handle; }
explicit operator bool() const noexcept {
return Traits::isValid(handle);
}
};
// 文件句柄特化
struct FileTraits {
using HandleType = FILE*;
static HandleType null() noexcept { return nullptr; }
static bool isValid(HandleType h) noexcept { return h != nullptr; }
static void release(HandleType h) noexcept { if(h) fclose(h); }
};
using FileHandle = ResourceHandle<FileTraits>;
11.2 作用域守卫模板
cpp复制template<typename Fn>
class ScopeGuard {
Fn fn;
bool active;
public:
explicit ScopeGuard(Fn&& f) : fn(std::forward<Fn>(f)), active(true) {}
~ScopeGuard() { if(active) fn(); }
void dismiss() noexcept { active = false; }
// 禁用拷贝
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
};
template<typename Fn>
ScopeGuard<Fn> make_scope_guard(Fn&& fn) {
return ScopeGuard<Fn>(std::forward<Fn>(fn));
}
void example() {
auto* ptr = new int(42);
auto guard = make_scope_guard([&] { delete ptr; });
// 某些操作...
if(success) {
guard.dismiss(); // 取消自动删除
}
// 否则ptr会被自动删除
}
11.3 基于策略的资源管理
cpp复制template<typename T, template<typename> class AllocPolicy>
class ManagedResource : private AllocPolicy<T> {
T* resource;
public:
template<typename... Args>
explicit ManagedResource(Args&&... args)
: resource(AllocPolicy<T>::allocate(std::forward<Args>(args)...)) {}
~ManagedResource() {
if(resource) AllocPolicy<T>::deallocate(resource);
}
// 移动语义等...
};
// 内存分配策略
template<typename T>
struct HeapAllocator {
static T* allocate() { return new T; }
static void deallocate(T* p) { delete p; }
};
// 文件资源策略
template<>
struct HeapAllocator<FILE> {
static FILE* allocate(const char* mode) { return fopen("file.txt", mode); }
static void deallocate(FILE* f) { if(f) fclose(f); }
};
void example() {
ManagedResource<int, HeapAllocator> num;
ManagedResource<FILE, HeapAllocator> file("r");
}
12. 性能关键系统中的异常处理
12.1 禁用异常的情况
在某些高性能场景,可能完全禁用异常(-fno-exceptions)。此时需要替代方案:
- 返回错误码
- 使用std::expected(C++23)
- 输出参数返回错误
- 终止程序(对于不可恢复错误)
cpp复制ErrorCode risky_operation(int arg, int* result) noexcept {
if(arg == 0) return ErrorCode::InvalidArgument;
*result = 100 / arg;
return ErrorCode::Success;
}
void example() {
int result;
if(auto err = risky_operation(10, &result); err != ErrorCode::Success) {
handle_error(err);
return;
}
use(result);
}
12.2 异常与零成本抽象
现代C++异常实现特点:
- 正常路径无额外开销
- 异常路径使用表驱动(.gcc_except_table)
- 抛出异常时查找匹配的catch块
优化建议:
- 将可能抛出异常的操作前置
- 缩小try块范围
- 对频繁失败的操作使用错误码
12.3 内存池与异常安全
cpp复制class MemoryPool {
struct Block {
Block* next;
};
Block* free_list;
std::vector<void*> allocated_chunks;
public:
void* allocate(size_t size) {
if(free_list) {
auto block = free_list;
free_list = free_list->next;
return block;
}
auto chunk = ::operator new(1024 * size);
allocated_chunks.push_back(chunk);
// 将新块加入空闲列表
auto p = static_cast<Block*>(chunk);
for(size_t i=0; i<1023; ++i) {
p->next = reinterpret_cast<Block*>(static_cast<char*>(chunk) + (i+1)*size);
p = p->next;
}
p->next = nullptr;
free_list = static_cast<Block*>(chunk);
return allocate(size); // 递归调用
}
void deallocate(void* p) noexcept {
if(!p) return;
auto block = static_cast<Block*>(p);
block->next = free_list;
free_list = block;
}
~MemoryPool() noexcept {
for(auto chunk : allocated_chunks) {
::operator delete(chunk);
}
}
};
关键点:
- 分配失败时抛出bad_alloc
- 析构函数确保释放所有内存
- 解分配操作不抛异常
13. 静态分析与工具支持
13.1 Clang静态分析器
检测常见问题:
- 资源泄漏
- 异常不安全路径
- 未捕获异常
使用示例:
bash复制clang --analyze -Xanalyzer -analyzer-output=text source.cpp
13.2 Coverity扫描
商业工具,检测:
- 异常控制流中的资源泄漏
- 未初始化的成员变量
- 无效的异常处理
13.3 clang-tidy检查
相关检查项:
- misc-exception-escape
- cert-err60-cpp(析构函数不应抛出)
- bugprone-exception-escape
配置示例:
yaml复制Checks: >
clang-analyzer*,
cert-err60-cpp,
bugprone-exception-escape
WarningsAsErrors: '*'
13.4 自定义clang插件
示例:检测不安全的析构函数
cpp复制class CheckDestructorThrow : public clang::ASTConsumer {
public:
void HandleTranslationUnit(clang::ASTContext& ctx) override {
auto& diag = ctx.getDiagnostics();
auto id = diag.getCustomDiagID(
clang::DiagnosticsEngine::Warning,
"destructor should be declared noexcept");
for(auto* decl : ctx.getTranslationUnitDecl()->decls()) {
if(auto* cxx = llvm::dyn_cast<clang::CXXRecordDecl>(decl)) {
if(auto* dtor = cxx->getDestructor()) {
if(!dtor->isNoexcept() &&
!dtor->hasAttr<clang::NoThrowAttr>()) {
diag.Report(dtor->getLocation(), id);
}
}
}
}
}
};
14. 设计模式与异常安全的结合
14.1 命令模式实现事务
cpp复制class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() noexcept = 0;
};
class Transaction {
std::vector<std::unique_ptr<Command>> commands;
public:
void add(std::unique_ptr<Command> cmd) {
commands.push_back(std::move(cmd));
}
void commit() {
std::vector<size_t> executed;
try {
for(size_t i=0; i<commands.size(); ++i) {
commands[i]->execute();
executed.push_back(i);
}
} catch(...) {
for(auto it=executed.rbegin(); it!=executed.rend(); ++it) {
commands[*it]->undo();
}
throw;
}
}
};
14.2 工厂模式与异常安全
cpp复制class Widget {
public:
virtual ~Widget() = default;
virtual void draw() const = 0;
};
class WidgetFactory {
public:
std::unique_ptr<Widget> create(const std::string& type) {
auto creator = get_creator(type);
try {
return creator();
} catch(...) {
log_error("Failed to create widget: " + type);
throw;
}
}
private:
using Creator = std::unique_ptr<Widget>(*)();
Creator get_creator(const std::string& type) {
static const std::map<std::string, Creator> creators = {
{"button", []{ return std::make_unique<Button>(); }},
{"menu", []{ return std::make_unique<Menu>(); }}
};
auto it = creators.find(type);
if(it == creators.end()) {
throw std::invalid_argument("Unknown widget type");
}
return it->second;
}
};
14.3 观察者模式的安全通知
cpp复制class Subject {
std::vector<std::weak_ptr<Observer>> observers;
std::mutex mtx;
public:
void notify() {
std::unique_lock<std::mutex> lock(mtx);
auto obs = observers; // 复制当前观察者列表
lock.unlock();
for(auto& weak_obs : obs) {
if(auto obs = weak_obs.lock()) {
try {
obs->update();
} catch(...) {
// 单个观察者失败不影响其他
log_exception(std::current_exception());
}
}
}
}
void add_observer(std::weak_ptr<Observer> obs) {
std::lock_guard<std::mutex> lock(mtx);
observers.push_back(obs);
}
};
15. 标准库中的异常安全实践
15.1 std::vector的实现细节
关键异常安全保证:
- 插入操作提供强保证(除非元素移动构造函数抛出)
- reserve()失败时保持原样
- 移动操作不抛异常
内部实现技巧:
- 先分配新内存,成功后再修改指针
- 使用std::move_if_noexcept
- 析构函数保证不抛
cpp复制template<typename T>
void vector<T>::push_back(const T& value) {
if(size_ == capacity_) {
auto new_cap = capacity_ ? 2 * capacity_ : 1;
auto new_data = alloc_traits::allocate(alloc_, new_cap);
try {
std::uninitialized_copy(
std::make_move_iterator_if_noexcept(begin()),
std::make_move_iterator_if_noexcept(end()),
new_data);
} catch(...) {
alloc_traits::deallocate(alloc_, new_data, new_cap);
throw;
}
// 所有复制成功,现在交换
destroy_elements();
alloc_traits::deallocate(alloc_, data_, capacity_);
data_ = new_data;
capacity_ = new_cap;
}
alloc_traits::construct(alloc_, data_ + size_, value);
++size_;
}
15.2 std::shared_ptr的原子操作
shared_ptr的线程安全保证:
- 引用计数是原子操作
- 控制块在堆上分配
- 从多个shared_ptr实例访问同一对象不需要额外同步
异常安全方面:
- make_shared提供强保证
- 构造函数可能抛出bad_alloc
- 解引用操作不抛异常
15.3 std::function的类型擦除
std::function的实现必须处理:
- 任意可调用对象的存储
- 类型安全的调用
- 异常安全的拷贝和移动
典型实现策略:
- 基