乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

RAII 与异常安全实践

定义与作用

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是 C++ 的核心惯用法:在构造函数中获取资源(内存、文件、锁等),在析构函数中释放资源。这种机制天然保证异常安全——即使异常导致栈展开,局部对象的析构函数也会被自动调用。

{
    std::lock_guard<std::mutex> lock(mtx);  // 构造:加锁
    // ... 临界区代码 ...
    // 即使抛异常,lock 析构时自动解锁
}

RAII 让资源管理从手动变为自动,从根本上杜绝资源泄露。

核心原理

RAII 与异常安全的关系

RAII 管理的资源类型

完整示例

示例一:文件处理与网络连接的综合 RAII 实践

场景说明:日志系统——同时打开文件、持有互斥锁、管理数据库连接。展示多层 RAII 的嵌套使用。

#include <iostream>
#include <fstream>
#include <string>
#include <mutex>
#include <memory>
#include <stdexcept>
#include <vector>
#include <chrono>
#include <ctime>
#include <sstream>

// 模拟数据库连接——RAII 包装
class DatabaseConnection {
public:
    DatabaseConnection(const std::string& db_name) : db_name_(db_name) {
        std::cout << "  [DB] 连接到 " << db_name_ << std::endl;
    }

    ~DatabaseConnection() {
        std::cout << "  [DB] 断开 " << db_name_ << std::endl;
    }

    void execute(const std::string& sql) {
        std::cout << "  [DB] 执行: " << sql << std::endl;
    }

    // 禁止拷贝
    DatabaseConnection(const DatabaseConnection&) = delete;
    DatabaseConnection& operator=(const DatabaseConnection&) = delete;

private:
    std::string db_name_;
};

// 日志系统——RAII 组合
class LogSystem {
public:
    LogSystem(const std::string& file_path, const std::string& db_name)
        : db_(std::make_unique<DatabaseConnection>(db_name)) {
        file_.open(file_path, std::ios::app);
        if (!file_.is_open())
            throw std::runtime_error("无法打开日志文件: " + file_path);
        std::cout << "  [日志系统] 初始化完成" << std::endl;
    }

    void log(const std::string& level, const std::string& message) {
        std::lock_guard<std::mutex> lock(mutex_);  // RAII 加锁

        auto now = std::chrono::system_clock::now();
        auto time = std::chrono::system_clock::to_time_t(now);
        char time_buf[32];
        std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S",
                      std::localtime(&time));

        file_ << "[" << time_buf << "] [" << level << "] " << message
              << std::endl;

        if (!file_.good())
            throw std::runtime_error("日志写入失败");

        if (level == "ERROR") {
            db_->execute("INSERT INTO error_log VALUES('"
                        + message + "')");
        }
    }

    // 析构函数——文件自动关闭,DB 自动断开,不需要手动操作

private:
    std::ofstream file_;                           // RAII 管理文件
    std::mutex mutex_;                             // 互斥锁
    std::unique_ptr<DatabaseConnection> db_;       // RAII 管理数据库连接
};

int main() {
    std::cout << "===== RAII 日志系统 =====" << std::endl;

    try {
        LogSystem logger("app.log", "logs.db");

        logger.log("INFO", "服务器启动");
        logger.log("INFO", "监听端口 8080");
        logger.log("WARN", "内存使用率 85%");
        logger.log("ERROR", "磁盘空间不足");

    } catch (const std::exception& e) {
        std::cerr << "日志系统异常: " << e.what() << std::endl;
        // 即使异常,所有资源已被 RAII 正确释放
    }

    std::cout << "  所有资源已安全释放" << std::endl;

    return 0;
}

预期输出:

===== RAII 日志系统 =====
  [DB] 连接到 logs.db
  [日志系统] 初始化完成
  [DB] 执行: INSERT INTO error_log VALUES('磁盘空间不足')
  [DB] 断开 logs.db
  所有资源已安全释放

逐段分析:

  • std::ofstream file_ 析构时自动关闭文件
  • std::lock_guard<std::mutex> 作用域结束时自动解锁
  • std::unique_ptr<DatabaseConnection> 析构时自动释放 DB 连接
  • 三层 RAII 嵌套——无论哪里抛异常,所有资源都会被正确释放
  • 成员析构顺序与声明顺序相反——先构造的后析构

示例二:自定义 RAII 资源管理

#include <iostream>
#include <memory>
#include <stdexcept>
#include <functional>

// ====== 自定义 RAII:临时目录管理 ======

class TempDirectory {
public:
    explicit TempDirectory(const std::string& prefix) {
        name_ = prefix + std::to_string(rand() % 10000);
        CreateDirectory(name_);
        std::cout << "  创建临时目录: " << name_ << std::endl;
    }

    ~TempDirectory() {
        RemoveDirectory(name_);
        std::cout << "  删除临时目录: " << name_ << std::endl;
    }

    const std::string& path() const { return name_; }

    TempDirectory(const TempDirectory&) = delete;
    TempDirectory& operator=(const TempDirectory&) = delete;

    TempDirectory(TempDirectory&& other) noexcept
        : name_(std::move(other.name_)) {
        other.name_.clear();
    }

private:
    static void CreateDirectory(const std::string&) { /* 系统调用 */ }
    static void RemoveDirectory(const std::string&) { /* 系统调用 */ }
    std::string name_;
};

// ====== 自定义 RAII:ScopeGuard(任意清理动作)======

class ScopeGuard {
public:
    template <typename Func>
    explicit ScopeGuard(Func&& f) : on_exit_(std::forward<Func>(f)), active_(true) {}

    ~ScopeGuard() {
        if (active_) on_exit_();
    }

    void dismiss() { active_ = false; }  // 取消清理

    ScopeGuard(const ScopeGuard&) = delete;
    ScopeGuard& operator=(const ScopeGuard&) = delete;

    ScopeGuard(ScopeGuard&& other) noexcept
        : on_exit_(std::move(other.on_exit_)), active_(other.active_) {
        other.dismiss();
    }

private:
    std::function<void()> on_exit_;
    bool active_;
};

// ====== 使用 ScopeGuard ======

void processData() {
    std::cout << "开始处理数据..." << std::endl;

    // 无论函数如何退出,都会打印"清理完成"
    auto guard = ScopeGuard([] {
        std::cout << "  [ScopeGuard] 清理完成" << std::endl;
    });

    // 模拟操作
    std::cout << "  处理中..." << std::endl;

    // 模拟异常
    if (rand() % 2 == 0) {
        std::cout << "  遇到异常!" << std::endl;
        throw std::runtime_error("数据处理失败");
    }

    std::cout << "  处理成功(正常路径)" << std::endl;
    // guard 析构时执行清理
}

int main() {
    std::srand(42);

    std::cout << "===== 尝试 1 =====" << std::endl;
    try {
        processData();
    } catch (const std::exception& e) {
        std::cerr << "捕获: " << e.what() << std::endl;
    }

    std::cout << "\n===== 尝试 2 =====" << std::endl;
    try {
        processData();
    } catch (const std::exception& e) {
        std::cerr << "捕获: " << e.what() << std::endl;
    }

    return 0;
}

预期输出(因随机,输出可能不同):

===== 尝试 1 =====
开始处理数据...
  处理中...
  遇到异常!
  [ScopeGuard] 清理完成
捕获: 数据处理失败

===== 尝试 2 =====
开始处理数据...
  处理中...
  处理成功(正常路径)
  [ScopeGuard] 清理完成

逐段分析:

  • ScopeGuard 是通用 RAII 包装——构造函数接收任意可调用对象,析构时执行
  • dismiss() 方法可取消清理动作——适合成功路径不需要"回滚"的场景
  • 无论 processData() 正常返回还是抛异常,guard 析构时都会执行清理
  • 这是 std::lock_guard、defer(Go)等机制的核心原理

易错场景与面试考点

易错场景

场景错误表现正确做法
裸 new/delete 管理资源异常时忘记 delete用 unique_ptr / shared_ptr
多资源顺序构造后构造的成员可能未析构用 RAII 对象包装每个资源
析构函数中抛异常terminate析构函数 catch 所有异常
忘记释放非内存资源文件句柄或锁泄漏用 RAII 包装所有资源类型
两个独立 new 之间异常第一个已分配未释放用智能指针或者在同一个构造函数中分配

常见面试问题

  1. RAII 是什么?如何保证异常安全?——在构造函数中获取资源(内存/文件/锁),在析构函数中释放。异常导致栈展开时,局部对象的析构函数会被自动调用——资源永不泄漏。

  2. RAII 和 GC(垃圾回收)的区别?——RAII 是确定性的(析构时机确定),管理所有资源类型;GC 只管内存,回收时机不确定。RAII 更适合 C++。

  3. ScopeGuard 模式是什么?——通用 RAII 包装:构造时传入任意清理函数,析构时执行。dismiss() 可取消。类似 Go 的 defer、Python 的 finally。

  4. 为什么成员变量的析构顺序重要?——C++ 保证成员按声明的反序析构。设计 RAII 类时,确保后析构的资源不依赖于先析构的资源。

  5. 异常安全的构造函数怎样写?——每个成员用 RAII 对象包装,或用智能指针。如果构造函数失败,已构造的成员自动析构,已分配的内存由智能指针自动释放。

小结

  • RAII 是 C++ 资源管理基石:构造获取,析构释放——异常自动安全
  • 标准库的 RAII 实践:unique_ptr / shared_ptr(内存)、lock_guard(锁)、fstream(文件)
  • ScopeGuard 模式可封装任意清理动作——通用 RAII 包装器
  • 永远不要裸露 new/delete,永远不要手动 unlock/close
  • 析构函数不应抛异常——C++11 起默认 noexcept
上一页
noexcept 与异常安全