在第 9 节中我们学习了new和delete来手动管理动态内存在第 13 节中我们了解了异常处理和 RAII 原则。今天我们来学习一个把这两者完美结合的工具——智能指针Smart Pointer。智能指针的核心理念很简单让对象自动管理内存程序员不需要手动调用delete。它是现代 C 中最重要的特性之一也是写出安全、不泄漏内存的代码的关键。1. 为什么需要智能指针1.1 裸指针的烦恼回顾一下使用裸指针raw pointer的问题voidrisky(){int*ptrnewint(42);// 如果这里发生了异常...doSomething();// 可能抛出异常deleteptr;// 这行可能永远执行不到}如果doSomething()抛出异常delete ptr不会被执行内存就泄漏了。虽然我们可以用try-catch来解决但代码会变得非常臃肿。1.2 智能指针的解决方案智能指针利用 RAII 原则在构造时获取资源new在析构时释放资源delete。当智能指针离开作用域时析构函数会自动被调用即使是因为异常导致的离开。#includememoryvoidsafe(){std::unique_ptrintptrstd::make_uniqueint(42);doSomething();// 即使抛出异常ptr 的析构函数也会自动调用// 不需要手动 delete}2. unique_ptr独占所有权2.1 基本用法unique_ptr是最常用的智能指针。它表示独占所有权——同一时刻只有一个unique_ptr拥有某个对象。#includeiostream#includememoryusingnamespacestd;intmain(){// 创建 unique_ptr推荐使用 make_uniqueunique_ptrintptrmake_uniqueint(42);// 通过 * 解引用访问值cout值*ptrendl;// 输出42// 通过 - 访问成员用于对象时// ptr-member// 离开作用域时自动释放内存return0;}2.2 不可复制只能移动unique_ptr不允许复制copy因为复制会导致两个指针指向同一块内存违反了「独占」的原则。但它允许移动move#includeiostream#includememoryusingnamespacestd;intmain(){unique_ptrintptr1make_uniqueint(42);// unique_ptrint ptr2 ptr1; // 编译错误不能复制unique_ptrintptr2std::move(ptr1);// 移动所有权// 移动后ptr1 变为空指针coutptr1(ptr1?有效:为空)endl;// 输出为空coutptr2*ptr2endl;// 输出42return0;}2.3 管理数组unique_ptr也可以管理动态数组#includeiostream#includememoryusingnamespacestd;intmain(){unique_ptrint[]arrmake_uniqueint[](5);for(inti0;i5;i){arr[i]i*10;}for(inti0;i5;i){coutarr[i] ;// 输出0 10 20 30 40}coutendl;// 自动调用 delete[] 释放return0;}2.4 自定义删除器有时候你需要在释放资源时执行自定义操作比如关闭文件、释放网络连接#includeiostream#includememory#includecstdiousingnamespacestd;intmain(){// 用 unique_ptr 管理 FILE*自定义删除器调用 fcloseunique_ptrFILE,decltype(fclose)file(fopen(test.txt,w),fclose);if(file){fprintf(file.get(),Hello, Smart Pointer!\n);}// file 离开作用域时自动调用 fclosereturn0;}3. shared_ptr共享所有权3.1 基本用法shared_ptr表示共享所有权——多个shared_ptr可以同时拥有同一个对象。它内部维护一个引用计数当最后一个shared_ptr被销毁时才会释放内存。#includeiostream#includememoryusingnamespacestd;intmain(){shared_ptrintptr1make_sharedint(42);cout引用计数ptr1.use_count()endl;// 输出1{shared_ptrintptr2ptr1;// 复制引用计数 1cout引用计数ptr1.use_count()endl;// 输出2}// ptr2 离开作用域引用计数 -1cout引用计数ptr1.use_count()endl;// 输出1return0;}// ptr1 离开作用域引用计数归零自动释放内存3.2 shared_ptr 的开销shared_ptr比unique_ptr有更多的内存和性能开销每个shared_ptr需要额外存储一个指向控制块的指针控制块中包含引用计数、弱引用计数、删除器等信息引用计数的增减是原子操作有微小的性能开销所以能用unique_ptr就不要用shared_ptr。3.3 make_shared vs new推荐使用make_shared而不是new来创建shared_ptr// 推荐一次内存分配对象和控制块一起分配autoptrmake_sharedint(42);// 不推荐两次内存分配一次 new 对象一次分配控制块shared_ptrintptr(newint(42));make_shared不仅代码更简洁而且性能更好减少一次内存分配。4. weak_ptr打破循环引用4.1 循环引用的问题shared_ptr有一个致命的陷阱——循环引用#includeiostream#includememoryusingnamespacestd;structB;// 前向声明structA{shared_ptrBb_ptr;~A(){coutA 被销毁endl;}};structB{shared_ptrAa_ptr;~B(){coutB 被销毁endl;}};intmain(){autoamake_sharedA();autobmake_sharedB();a-b_ptrb;// A 指向 Bb-a_ptra;// B 指向 A// 离开作用域后a 的引用计数 1被 b-a_ptr 引用// b 的引用计数 1被 a-b_ptr 引用// 两者都不会归零内存泄漏return0;}运行这段代码你会发现A 和 B 的析构函数都不会被调用——内存泄漏了。4.2 weak_ptr 的解决方案weak_ptr是shared_ptr的「观察者」它不增加引用计数。通过weak_ptr可以打破循环引用#includeiostream#includememoryusingnamespacestd;structB;structA{shared_ptrBb_ptr;~A(){coutA 被销毁endl;}};structB{weak_ptrAa_ptr;// 改用 weak_ptr~B(){coutB 被销毁endl;}};intmain(){autoamake_sharedA();autobmake_sharedB();a-b_ptrb;b-a_ptra;return0;}// 输出// A 被销毁// B 被销毁4.3 使用 weak_ptr 访问对象weak_ptr不能直接访问对象需要先调用lock()获取一个shared_ptrweak_ptrintwp...;// 使用前先检查对象是否还存在if(autospwp.lock()){cout对象存在值为*spendl;}else{cout对象已被销毁endl;}lock()的行为如果对象还存在引用计数 0返回一个有效的shared_ptr如果对象已被销毁返回一个空的shared_ptr5. 三种智能指针对比特性unique_ptrshared_ptrweak_ptr所有权独占共享不拥有观察可复制否是是可移动是是-引用计数无有不增加计数内存开销最小较大控制块较小典型场景独占资源管理共享资源打破循环引用创建方式make_uniquemake_shared从shared_ptr创建6. 使用建议6.1 优先使用 unique_ptr如果你的场景中只有一个指针拥有对象用unique_ptr。它没有额外开销语义也最清晰。6.2 需要共享时用 shared_ptr当多个地方需要共享同一个对象且对象的生命周期不确定时用shared_ptr。6.3 用 weak_ptr 打破循环如果两个shared_ptr互相引用把其中一个改为weak_ptr。6.4 函数参数传递// 只读访问传引用或裸指针voidprocess(constWidgetwidget);voidprocess(Widget*widget);// 接收所有权传 unique_ptr按值voidtakeOwnership(unique_ptrWidgetwidget);// 共享所有权传 shared_ptr按值或按引用voidshare(shared_ptrWidgetwidget);6.5 不要混用裸指针和智能指针// 错误两个独立的智能指针管理同一块内存int*rawnewint(42);unique_ptrintp1(raw);unique_ptrintp2(raw);// p1 和 p2 都会 delete raw双重释放// 正确只通过智能指针创建autop1make_uniqueint(42);7. 总结这一节我们学习了 C 的三种智能指针unique_ptr独占所有权不可复制推荐首选。shared_ptr共享所有权引用计数管理有额外开销。weak_ptr观察者不增加引用计数用于打破循环引用。智能指针是现代 C 内存管理的基石。掌握了它们你就再也不需要担心内存泄漏和悬垂指针的问题。下一节我们将继续探索 C 的更多高级特性。加油
【C++】零基础入门 · 第 14 节:智能指针(unique_ptr、shared_ptr、weak_ptr)
发布时间:2026/5/31 20:43:02
在第 9 节中我们学习了new和delete来手动管理动态内存在第 13 节中我们了解了异常处理和 RAII 原则。今天我们来学习一个把这两者完美结合的工具——智能指针Smart Pointer。智能指针的核心理念很简单让对象自动管理内存程序员不需要手动调用delete。它是现代 C 中最重要的特性之一也是写出安全、不泄漏内存的代码的关键。1. 为什么需要智能指针1.1 裸指针的烦恼回顾一下使用裸指针raw pointer的问题voidrisky(){int*ptrnewint(42);// 如果这里发生了异常...doSomething();// 可能抛出异常deleteptr;// 这行可能永远执行不到}如果doSomething()抛出异常delete ptr不会被执行内存就泄漏了。虽然我们可以用try-catch来解决但代码会变得非常臃肿。1.2 智能指针的解决方案智能指针利用 RAII 原则在构造时获取资源new在析构时释放资源delete。当智能指针离开作用域时析构函数会自动被调用即使是因为异常导致的离开。#includememoryvoidsafe(){std::unique_ptrintptrstd::make_uniqueint(42);doSomething();// 即使抛出异常ptr 的析构函数也会自动调用// 不需要手动 delete}2. unique_ptr独占所有权2.1 基本用法unique_ptr是最常用的智能指针。它表示独占所有权——同一时刻只有一个unique_ptr拥有某个对象。#includeiostream#includememoryusingnamespacestd;intmain(){// 创建 unique_ptr推荐使用 make_uniqueunique_ptrintptrmake_uniqueint(42);// 通过 * 解引用访问值cout值*ptrendl;// 输出42// 通过 - 访问成员用于对象时// ptr-member// 离开作用域时自动释放内存return0;}2.2 不可复制只能移动unique_ptr不允许复制copy因为复制会导致两个指针指向同一块内存违反了「独占」的原则。但它允许移动move#includeiostream#includememoryusingnamespacestd;intmain(){unique_ptrintptr1make_uniqueint(42);// unique_ptrint ptr2 ptr1; // 编译错误不能复制unique_ptrintptr2std::move(ptr1);// 移动所有权// 移动后ptr1 变为空指针coutptr1(ptr1?有效:为空)endl;// 输出为空coutptr2*ptr2endl;// 输出42return0;}2.3 管理数组unique_ptr也可以管理动态数组#includeiostream#includememoryusingnamespacestd;intmain(){unique_ptrint[]arrmake_uniqueint[](5);for(inti0;i5;i){arr[i]i*10;}for(inti0;i5;i){coutarr[i] ;// 输出0 10 20 30 40}coutendl;// 自动调用 delete[] 释放return0;}2.4 自定义删除器有时候你需要在释放资源时执行自定义操作比如关闭文件、释放网络连接#includeiostream#includememory#includecstdiousingnamespacestd;intmain(){// 用 unique_ptr 管理 FILE*自定义删除器调用 fcloseunique_ptrFILE,decltype(fclose)file(fopen(test.txt,w),fclose);if(file){fprintf(file.get(),Hello, Smart Pointer!\n);}// file 离开作用域时自动调用 fclosereturn0;}3. shared_ptr共享所有权3.1 基本用法shared_ptr表示共享所有权——多个shared_ptr可以同时拥有同一个对象。它内部维护一个引用计数当最后一个shared_ptr被销毁时才会释放内存。#includeiostream#includememoryusingnamespacestd;intmain(){shared_ptrintptr1make_sharedint(42);cout引用计数ptr1.use_count()endl;// 输出1{shared_ptrintptr2ptr1;// 复制引用计数 1cout引用计数ptr1.use_count()endl;// 输出2}// ptr2 离开作用域引用计数 -1cout引用计数ptr1.use_count()endl;// 输出1return0;}// ptr1 离开作用域引用计数归零自动释放内存3.2 shared_ptr 的开销shared_ptr比unique_ptr有更多的内存和性能开销每个shared_ptr需要额外存储一个指向控制块的指针控制块中包含引用计数、弱引用计数、删除器等信息引用计数的增减是原子操作有微小的性能开销所以能用unique_ptr就不要用shared_ptr。3.3 make_shared vs new推荐使用make_shared而不是new来创建shared_ptr// 推荐一次内存分配对象和控制块一起分配autoptrmake_sharedint(42);// 不推荐两次内存分配一次 new 对象一次分配控制块shared_ptrintptr(newint(42));make_shared不仅代码更简洁而且性能更好减少一次内存分配。4. weak_ptr打破循环引用4.1 循环引用的问题shared_ptr有一个致命的陷阱——循环引用#includeiostream#includememoryusingnamespacestd;structB;// 前向声明structA{shared_ptrBb_ptr;~A(){coutA 被销毁endl;}};structB{shared_ptrAa_ptr;~B(){coutB 被销毁endl;}};intmain(){autoamake_sharedA();autobmake_sharedB();a-b_ptrb;// A 指向 Bb-a_ptra;// B 指向 A// 离开作用域后a 的引用计数 1被 b-a_ptr 引用// b 的引用计数 1被 a-b_ptr 引用// 两者都不会归零内存泄漏return0;}运行这段代码你会发现A 和 B 的析构函数都不会被调用——内存泄漏了。4.2 weak_ptr 的解决方案weak_ptr是shared_ptr的「观察者」它不增加引用计数。通过weak_ptr可以打破循环引用#includeiostream#includememoryusingnamespacestd;structB;structA{shared_ptrBb_ptr;~A(){coutA 被销毁endl;}};structB{weak_ptrAa_ptr;// 改用 weak_ptr~B(){coutB 被销毁endl;}};intmain(){autoamake_sharedA();autobmake_sharedB();a-b_ptrb;b-a_ptra;return0;}// 输出// A 被销毁// B 被销毁4.3 使用 weak_ptr 访问对象weak_ptr不能直接访问对象需要先调用lock()获取一个shared_ptrweak_ptrintwp...;// 使用前先检查对象是否还存在if(autospwp.lock()){cout对象存在值为*spendl;}else{cout对象已被销毁endl;}lock()的行为如果对象还存在引用计数 0返回一个有效的shared_ptr如果对象已被销毁返回一个空的shared_ptr5. 三种智能指针对比特性unique_ptrshared_ptrweak_ptr所有权独占共享不拥有观察可复制否是是可移动是是-引用计数无有不增加计数内存开销最小较大控制块较小典型场景独占资源管理共享资源打破循环引用创建方式make_uniquemake_shared从shared_ptr创建6. 使用建议6.1 优先使用 unique_ptr如果你的场景中只有一个指针拥有对象用unique_ptr。它没有额外开销语义也最清晰。6.2 需要共享时用 shared_ptr当多个地方需要共享同一个对象且对象的生命周期不确定时用shared_ptr。6.3 用 weak_ptr 打破循环如果两个shared_ptr互相引用把其中一个改为weak_ptr。6.4 函数参数传递// 只读访问传引用或裸指针voidprocess(constWidgetwidget);voidprocess(Widget*widget);// 接收所有权传 unique_ptr按值voidtakeOwnership(unique_ptrWidgetwidget);// 共享所有权传 shared_ptr按值或按引用voidshare(shared_ptrWidgetwidget);6.5 不要混用裸指针和智能指针// 错误两个独立的智能指针管理同一块内存int*rawnewint(42);unique_ptrintp1(raw);unique_ptrintp2(raw);// p1 和 p2 都会 delete raw双重释放// 正确只通过智能指针创建autop1make_uniqueint(42);7. 总结这一节我们学习了 C 的三种智能指针unique_ptr独占所有权不可复制推荐首选。shared_ptr共享所有权引用计数管理有额外开销。weak_ptr观察者不增加引用计数用于打破循环引用。智能指针是现代 C 内存管理的基石。掌握了它们你就再也不需要担心内存泄漏和悬垂指针的问题。下一节我们将继续探索 C 的更多高级特性。加油