【c++面向对象编程】第30篇:RAII与智能指针(一):auto_ptr的缺陷与unique_ptr
目录一、一个手动管理的痛点二、RAII 核心思想三、auto_ptrC98 的尝试与缺陷auto_ptr 的核心缺陷四、unique_ptr真正的独占式智能指针基本用法常用成员函数五、unique_ptr 与数组六、自定义删除器七、make_uniqueC14八、完整例子对比手动管理 vs unique_ptr九、unique_ptr 的常见误区误区1试图拷贝 unique_ptr误区2用 get() 获取裸指针后 delete误区3release() 后忘记释放内存误区4用裸指针重新赋值 unique_ptr十、这一篇的收获一、一个手动管理的痛点cppvoid riskyFunction() { int* p new int[1000]; // ... 做一些操作 ... if (something_wrong) { return; // 忘记 delete[] → 内存泄漏 } delete[] p; }问题在于delete很容易被遗忘特别是在有多个返回路径或异常抛出的情况下。RAII 解决方案让对象自动管理资源。cppvoid safeFunction() { vectorint v(1000); // 内存由 vector 管理 // ... 无论怎么返回v 的析构函数都会释放内存 }这就是 RAII 的精髓将资源的生命周期与对象的生命周期绑定。二、RAII 核心思想RAIIResource Acquisition Is Initialization的含义获取资源即初始化在构造函数中获取资源分配内存、打开文件、加锁等释放资源即析构在析构函数中释放资源释放内存、关闭文件、解锁等对象生命周期结束资源自动释放cppclass FileHandle { FILE* file; public: FileHandle(const char* filename) { file fopen(filename, r); } ~FileHandle() { if (file) fclose(file); // 自动关闭 } // 禁止拷贝后面解释 };只要FileHandle对象存在文件就是打开的对象销毁时文件自动关闭。不再需要手动调用fclose。三、auto_ptrC98 的尝试与缺陷C98 引入了auto_ptr试图实现 RAII 式的动态内存管理。cpp#include memory // auto_ptr 在这里 auto_ptrint p1(new int(42)); auto_ptrint p2 p1; // 看起来像拷贝但实际是转移所有权 // 此时 p1 已经变成 nullptrauto_ptr 的核心缺陷auto_ptr的拷贝构造函数和赋值运算符会转移所有权被复制的指针变成nullptr。这违反了正常的拷贝语义cppauto_ptrstring p1(new string(hello)); auto_ptrstring p2(p1); // p1 被置空 cout *p1; // 崩溃p1 已经是空指针更严重的问题auto_ptr不能用于 STL 容器因为容器要求元素有正常的拷贝行为。cppvectorauto_ptrint vec; // 能编译但绝对不要这样做 vec.push_back(auto_ptrint(new int(1))); vec.push_back(auto_ptrint(new int(2))); // 容器内部拷贝时所有权转移导致原始指针变空非常容易出错结论auto_ptr在 C11 中被正式废弃deprecatedC17 中已移除。永远不要用它。四、unique_ptr真正的独占式智能指针unique_ptr是auto_ptr的现代替代品核心特点独占所有权一个资源只能被一个unique_ptr拥有禁止拷贝拷贝构造函数和拷贝赋值被delete支持移动通过移动语义转移所有权零开销与裸指针大小相同通常 8 字节没有额外虚函数开销基本用法cpp#include memory using namespace std; unique_ptrint p1(new int(42)); unique_ptrint p2 move(p1); // 转移所有权p1 变为空 if (!p1) cout p1 为空 endl; cout *p2 endl; // 42 // ❌ 编译错误不能拷贝 // unique_ptrint p3 p1;常用成员函数函数作用get()返回裸指针不转移所有权release()释放所有权返回裸指针不 deletereset()释放当前对象并可选接管新指针reset(nullptr)释放当前对象置空operator bool()判断是否为空cppunique_ptrint p(new int(100)); int* raw p.release(); // p 放弃所有权变为 nullptr delete raw; // 需要手动释放 p.reset(new int(200)); // 释放旧对象接管新对象 p.reset(); // 释放对象置空 if (p) { cout p 非空 endl; }五、unique_ptr 与数组unique_ptr支持数组版本使用T[]模板参数cppunique_ptrint[] arr(new int[10]); arr[0] 42; // 可以用下标访问 // 不需要手动 delete[] // 离开作用域时自动调用 delete[]注意unique_ptrT用deleteunique_ptrT[]用delete[]编译器会自动选择正确的释放方式。六、自定义删除器有时资源不是new分配的如fopen、malloc、socket需要自定义释放方式。cpp#include memory #include cstdio using namespace std; // 自定义删除器函数对象 struct FileDeleter { void operator()(FILE* f) const { if (f) { fclose(f); cout 文件已关闭 endl; } } }; int main() { unique_ptrFILE, FileDeleter file(fopen(test.txt, r)); // 自动调用 fclose不需要手动关闭 return 0; }使用 lambda 作为删除器更简洁cppauto deleter [](FILE* f) { if (f) fclose(f); }; unique_ptrFILE, decltype(deleter) file(fopen(test.txt, r), deleter);七、make_uniqueC14C14 提供了make_unique更安全、更高效地创建unique_ptrcpp// C11 方式 unique_ptrint p1(new int(42)); // C14 方式推荐 auto p2 make_uniqueint(42); auto p3 make_uniqueint[](10); // 数组版本优势异常安全避免new和unique_ptr构造之间的空隙代码更简洁减少重复类型名八、完整例子对比手动管理 vs unique_ptrcpp#include iostream #include memory #include vector using namespace std; class Resource { int id; public: Resource(int i) : id(i) { cout Resource id 已获取 endl; } ~Resource() { cout Resource id 已释放 endl; } void use() { cout 使用 Resource id endl; } }; // 手动管理容易出错 void manualManagement() { cout 手动管理 endl; Resource* r new Resource(1); r-use(); // 忘记 delete → 内存泄漏 // 即使记得如果中间抛异常也会泄漏 } // unique_ptr 管理自动释放 void uniquePtrManagement() { cout \n unique_ptr 管理 endl; auto r make_uniqueResource(2); r-use(); // 函数结束自动释放 } // 所有权转移示例 void ownershipTransfer() { cout \n 所有权转移 endl; unique_ptrResource p1 make_uniqueResource(3); cout p1 拥有资源 endl; unique_ptrResource p2 move(p1); // 转移所有权 cout 转移后: p1 (p1 ? 非空 : 空) endl; cout p2 拥有资源 endl; p2-use(); // p2 释放资源 } // 放入容器 void containerUsage() { cout \n 放入容器 endl; vectorunique_ptrResource vec; vec.push_back(make_uniqueResource(4)); vec.push_back(make_uniqueResource(5)); vec.push_back(make_uniqueResource(6)); for (auto p : vec) { p-use(); } // 容器销毁时所有 unique_ptr 自动释放资源 } int main() { // manualManagement(); // 会泄漏不运行 uniquePtrManagement(); ownershipTransfer(); containerUsage(); return 0; }输出text unique_ptr 管理 Resource 2 已获取 使用 Resource 2 Resource 2 已释放 所有权转移 Resource 3 已获取 p1 拥有资源 转移后: p1 空 p2 拥有资源 使用 Resource 3 Resource 3 已释放 放入容器 Resource 4 已获取 Resource 5 已获取 Resource 6 已获取 使用 Resource 4 使用 Resource 5 使用 Resource 6 Resource 6 已释放 Resource 5 已释放 Resource 4 已释放九、unique_ptr 的常见误区误区1试图拷贝 unique_ptrcppunique_ptrint p1(new int(5)); unique_ptrint p2 p1; // ❌ 编译错误误区2用get()获取裸指针后 deletecppunique_ptrint p(new int(5)); int* raw p.get(); delete raw; // ❌ 重复释放p 析构时会再次 delete误区3release()后忘记释放内存cppauto p make_uniqueint(42); int* raw p.release(); // p 放弃所有权 // 没有 delete raw → 内存泄漏误区4用裸指针重新赋值unique_ptrcppunique_ptrint p(new int(5)); p new int(6); // ❌ 编译错误不支持隐式转换 p.reset(new int(6)); // ✅ 正确十、这一篇的收获你现在应该理解RAII构造函数获取资源析构函数释放资源对象生命周期绑定资源auto_ptr缺陷拷贝转移所有权违反直觉不能用于容器已被废弃unique_ptr独占所有权禁止拷贝支持移动零开销常用操作make_unique、reset、release、get、移动语义转移所有权数组支持unique_ptrT[]自动调用delete[]自定义删除器可管理fopen、malloc等非new资源 小作业写一个unique_ptrFILE, CustomDeleter管理打开的文件验证函数返回或异常时文件自动关闭。然后尝试写一段代码演示move转移所有权后原指针变为空。下一篇预告第31篇《智能指针二shared_ptr与weak_ptr——循环引用破解》——多个对象需要共享同一资源时用shared_ptr。但两个shared_ptr互相引用会导致资源永远无法释放weak_ptr正是破解循环引用的利器。下篇详解。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2622947.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!