网站制作一条龙全包关于我们 网站
2026/3/30 18:21:20 网站建设 项目流程
网站制作一条龙全包,关于我们 网站,软件平台搭建包括哪几个方面,在wordpress上添加播放视频第三章 异常(一) 条款9#xff1a;利用destructors避免泄露资源 一、核心概念解析 首先#xff0c;我们要理解这个条款解决的核心问题#xff1a;手动管理资源#xff08;如内存、文件句柄、网络连接等#xff09;时#xff0c;容易因忘记释放、程序提前退出#xff08;…第三章 异常(一)条款9利用destructors避免泄露资源一、核心概念解析首先我们要理解这个条款解决的核心问题手动管理资源如内存、文件句柄、网络连接等时容易因忘记释放、程序提前退出如异常等原因导致资源泄露。C 的析构函数destructor有一个关键特性当一个对象的生命周期结束如离开作用域、被 delete时其析构函数会自动调用。利用这个特性我们可以将资源的释放逻辑封装到析构函数中让资源的生命周期与对象绑定 —— 这就是 RAIIResource Acquisition Is Initialization资源获取即初始化的核心思想。问题场景手动管理资源的风险先看一个反例直观感受资源泄露的问题#include iostream #include string // 模拟一个需要手动释放的资源如动态内存 void createResource(std::string* ptr) { ptr new std::string(我是需要释放的资源); } void releaseResource(std::string* ptr) { delete ptr; ptr nullptr; } void riskyFunction(bool throwException) { std::string* res nullptr; createResource(res); // 获取资源 // 模拟业务逻辑如果抛出异常后续的releaseResource不会执行 if (throwException) { throw std::runtime_error(业务逻辑异常); } // 即使没有异常也可能忘记写这行导致内存泄露 releaseResource(res); } int main() { try { riskyFunction(true); // 传入true触发异常资源泄露 } catch (const std::exception e) { std::cout 捕获异常 e.what() std::endl; } // 程序结束后res指向的内存未被释放发生泄露 return 0; }问题分析1.如果riskyFunction中抛出异常releaseResource不会执行资源泄露2.即使没有异常手动调用releaseResource容易遗漏导致泄露3.代码需要手动配对 “获取 - 释放”心智负担重。二、解决方案用析构函数自动释放资源我们可以封装一个资源管理类在构造函数中获取资源析构函数中释放资源。只要这个类的对象离开作用域析构函数就会自动调用资源被释放从根本上避免泄露。代码示例实现一个简单的资源管理类#include iostream #include string #include stdexcept // 资源管理类遵循RAII原则 class ResourceGuard { private: std::string* m_resource; // 管理的资源这里以动态字符串为例 public: // 构造函数获取资源资源获取即初始化 explicit ResourceGuard(const std::string content) : m_resource(new std::string(content)) { std::cout 资源已获取地址 m_resource std::endl; } // 析构函数自动释放资源无论正常退出还是异常退出 ~ResourceGuard() { if (m_resource ! nullptr) { delete m_resource; m_resource nullptr; std::cout 资源已释放 std::endl; } } // 禁用拷贝构造和拷贝赋值避免浅拷贝导致重复释放 ResourceGuard(const ResourceGuard) delete; ResourceGuard operator(const ResourceGuard) delete; // 提供访问资源的接口可选 std::string getResource() const { if (m_resource nullptr) { throw std::runtime_error(资源已释放); } return *m_resource; } }; // 安全的函数使用资源管理类 void safeFunction(bool throwException) { // 创建资源管理对象构造函数获取资源 ResourceGuard guard(我是受保护的资源); // 模拟业务逻辑即使抛出异常guard的析构函数仍会执行 if (throwException) { throw std::runtime_error(业务逻辑异常但资源不会泄露); } // 正常使用资源 std::cout 资源内容 guard.getResource() std::endl; // 函数结束时guard离开作用域析构函数自动释放资源 } int main() { try { safeFunction(true); // 触发异常 } catch (const std::exception e) { std::cout 捕获异常 e.what() std::endl; } std::cout 程序正常结束 std::endl; return 0; }关键细节解释1.RAII 核心ResourceGuard的构造函数负责 “获取资源”析构函数负责 “释放资源”资源的生命周期与guard对象绑定2.异常安全即使safeFunction抛出异常guard对象的析构函数仍会被调用C 保证栈上对象的析构函数在异常展开时执行资源不会泄露3.禁用拷贝如果允许拷贝多个ResourceGuard对象会管理同一份资源析构时会重复释放导致崩溃因此禁用拷贝构造和拷贝赋值C11 后也可使用移动语义4.通用性这个思路不仅适用于内存还适用于文件句柄、锁、网络连接等所有需要手动释放的资源比如std::fstream自动关闭文件、std::lock_guard自动释放锁都是这个原理。进阶使用标准库的智能指针更推荐实际开发中我们不需要自己写资源管理类C 标准库提供了现成的智能指针std::unique_ptr/std::shared_ptr它们的底层就是利用析构函数自动释放资源#include iostream #include string #include memory // 包含智能指针头文件 #include stdexcept void smarterFunction(bool throwException) { // std::unique_ptr独占式智能指针析构时自动delete std::unique_ptrstd::string res std::make_uniquestd::string(智能指针管理的资源); if (throwException) { throw std::runtime_error(异常发生但智能指针会自动释放资源); } std::cout 资源内容 *res std::endl; } int main() { try { smarterFunction(true); } catch (const std::exception e) { std::cout 捕获异常 e.what() std::endl; } return 0; }std::unique_ptr是条款 9 的最佳实践落地 —— 它完全遵循 RAII无需手动管理且性能几乎与裸指针一致。总结核心思想将资源的释放逻辑封装到析构函数中利用析构函数 “自动调用” 的特性避免手动释放资源的遗漏或异常导致的泄露RAII 原则关键做法不要直接管理裸资源而是用对象如自定义资源管理类、标准库智能指针包裹资源让对象的生命周期与资源绑定实践推荐优先使用 C 标准库提供的智能指针std::unique_ptr/std::shared_ptr而非手写资源管理类避免重复造轮子且更安全。条款10在constructors内阻止资源泄露(resource leak)一、核心问题构造函数的特殊性C 的构造函数没有返回值且如果在构造过程中抛出异常当前对象的析构函数不会被调用。这意味着如果构造函数中分配了资源如动态内存、文件句柄、锁、网络连接等但在资源分配后、构造完成前抛出了异常这些已分配的资源就无法被析构函数释放从而导致资源泄露。二、解决方案RAII资源获取即初始化条款 10 的核心解决方案是RAIIResource Acquisition Is Initialization将资源的生命周期绑定到对象的生命周期 —— 资源在对象构造时获取在对象析构时释放。具体来说1.把资源封装到独立的 “资源管理类” 中2.在构造函数中只创建资源管理类的对象而非直接操作裸资源3.即使构造函数抛出异常资源管理类的析构函数仍会被调用从而保证资源释放。三、代码示例反例有资源泄露 正例无泄露#include iostream #include stdexcept using namespace std; // 模拟一个需要手动释放的资源如动态内存、文件句柄 class Resource { public: Resource() { cout Resource 分配成功\n; } ~Resource() { cout Resource 释放成功\n; } // 析构释放资源 void use() const { /* 资源使用逻辑 */ } }; // 有资源泄露风险的类 class BadClass { private: Resource* res1; // 裸指针管理资源1 Resource* res2; // 裸指针管理资源2 public: BadClass() { // 第一步分配资源1成功 res1 new Resource(); // 第二步模拟构造过程中抛出异常比如资源2分配失败、逻辑错误 throw runtime_error(构造函数执行中发生异常); // 第三步分配资源2永远不会执行 res2 new Resource(); } ~BadClass() { // 析构函数不会被调用因为构造函数抛异常对象未完全构造 delete res1; delete res2; cout BadClass 析构释放所有资源\n; } }; int main() { try { BadClass obj; // 构造时抛异常 } catch (const exception e) { cout 捕获异常 e.what() endl; } // 输出Resource 分配成功 → 捕获异常 → 无Resource 释放成功 // 结论res1的资源永远无法释放造成泄露 return 0; }2. 正例用 RAII 封装资源解决泄露核心思路用智能指针如 std::unique_ptr替代裸指针 —— 智能指针是 RAII 的典型实现其析构函数会自动释放管理的资源即使构造函数抛异常。#include iostream #include stdexcept #include memory // 包含智能指针头文件 using namespace std; // 待管理的资源同上 class Resource { public: Resource() { cout Resource 分配成功\n; } ~Resource() { cout Resource 释放成功\n; } void use() const { /* 资源使用逻辑 */ } }; // 安全的类用RAII智能指针管理资源 class GoodClass { private: // 用std::unique_ptr独占所有权替代裸指针自动管理资源 unique_ptrResource res1; unique_ptrResource res2; public: GoodClass() { // 第一步分配资源1封装到unique_ptr中 res1 make_uniqueResource(); // C14及以上等价于 unique_ptrResource(new Resource()) // 第二步模拟构造过程中抛异常 throw runtime_error(构造函数执行中发生异常); // 第三步分配资源2不会执行 res2 make_uniqueResource(); } ~GoodClass() { // 即使析构函数不手动释放unique_ptr也会自动释放资源 cout GoodClass 析构\n; } }; int main() { try { GoodClass obj; // 构造时抛异常 } catch (const exception e) { cout 捕获异常 e.what() endl; } // 输出Resource 分配成功 → 捕获异常 → Resource 释放成功 // 结论res1的资源被unique_ptr的析构函数自动释放无泄露 return 0; }3. 扩展自定义 RAII 资源管理类理解底层原理如果需要管理非内存资源如文件句柄、锁可以自定义 RAII 类#include iostream #include stdexcept #include cstdio // FILE相关头文件 using namespace std; // 自定义RAII类管理文件句柄非内存资源 class FileHandle { private: FILE* file; // 裸句柄仅在RAII类内部使用 public: // 构造获取资源打开文件 FileHandle(const char* filename, const char* mode) { file fopen(filename, mode); if (!file) { throw runtime_error(文件打开失败); } cout 文件 filename 打开成功\n; } // 析构释放资源关闭文件 ~FileHandle() { if (file) { fclose(file); cout 文件关闭成功\n; } } // 禁用拷贝避免资源重复释放 FileHandle(const FileHandle) delete; FileHandle operator(const FileHandle) delete; // 提供资源访问接口 FILE* get() const { return file; } }; // 使用自定义RAII类的业务类 class FileProcessor { private: FileHandle fh; // RAII对象绑定文件资源 public: FileProcessor(const char* filename) : fh(filename, w) { // 模拟构造过程中抛异常 throw runtime_error(FileProcessor构造异常); } }; int main() { try { FileProcessor fp(test.txt); } catch (const exception e) { cout 捕获异常 e.what() endl; } // 输出文件 test.txt 打开成功 → 捕获异常 → 文件关闭成功 // 结论即使构造抛异常FileHandle的析构仍会执行文件句柄无泄露 return 0; }总结核心风险构造函数抛异常时对象未完全构造析构函数不会执行直接管理的裸资源会泄露核心方案采用RAII思想将资源封装到 “资源管理类” 中如 std::unique_ptr、std::shared_ptr或自定义 RAII 类利用资源管理类的析构函数自动释放资源最佳实践在构造函数中避免直接操作裸资源优先使用标准库提供的智能指针自定义资源如文件、锁需封装为独立 RAII 类杜绝构造过程中的资源泄露。关键点回顾构造函数抛异常 → 析构函数不执行 → 裸资源泄露RAII资源绑定到对象生命周期构造获取、析构释放智能指针是 RAII 的 “现成方案”自定义 RAII 类适配非内存资源。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询