# 智能指针深度剖析从资源泄漏到循环引用手写 auto_ptr、unique_ptr、shared_ptr、weak_ptr 管理动态内存一直是 C 程序员的噩梦。忘记 delete 会导致内存泄漏异常抛出会让代码跳过清理逻辑对象所有权不清晰更是万恶之源。C 的解决之道就是**智能指针**——它们是 RAII 思想的完美体现在构造函数中获取资源在析构函数中释放资源让资源生命周期与对象生命周期严格绑定。 本文将用**手写的简化版智能指针**先讲清楚资源泄漏与内存泄漏的概念再一步步讲解 auto_ptr、unique_ptr、shared_ptr、weak_ptr 的实现原理以及它们常用接口、赋值行为和删除器传递方式。所有代码都可以编译运行建议你亲自调试一遍加深理解。 ## 1. 资源泄漏与内存泄漏 在讨论智能指针之前必须先明确两个概念 ### 1.1 内存泄漏 **内存泄漏**指程序在堆上分配了内存但在使用完毕后没有释放导致该内存无法被再次利用。随着程序运行泄漏的内存不断累积最终可能耗尽系统内存。 cpp void leak() { int* p new int(42); // 分配内存 // 忘记 delete p // 内存泄漏 }如果函数内发生异常情况更糟voiddanger(){int*pnewint(42);throwstd::runtime_error(oops);deletep;// 永远执行不到内存泄漏}1.2 资源泄漏广义资源泄漏是更广泛的概念指任何需要手动释放的系统资源未能正确释放包括但不限于动态分配的内存new/malloc未对应delete/free文件句柄fopen未fclose互斥锁mutex.lock()未unlock()网络套接字、数据库连接等内存泄漏是资源泄漏中最常见的一种。C 的 RAII 思想正是为了解决所有资源泄漏而设计的把资源生命周期绑定到局部对象上利用析构函数的自动调用特性保证资源释放。智能指针是 RAII 在动态内存管理上的直接应用。2. auto_ptr充满陷阱的“半成品”C98 提供了std::auto_ptr它尝试用“所有权转移”来管理动态内存。我们手写的pz::auto_ptr复刻了它的核心行为templateclassTclassauto_ptr{public:auto_ptr(T*ptrnullptr):_ptr(ptr){}auto_ptr(auto_ptrTptr):_ptr(ptr._ptr){ptr._ptrnullptr;// 拷贝构造时“窃取”源对象资源}auto_ptrToperator(auto_ptrTptr){if(ptr._ptr){_ptrptr._ptr;ptr._ptrnullptr;// 赋值时也“窃取”资源}return*this;}~auto_ptr(){delete_ptr;}Toperator*(){return*_ptr;}T*operator-(){return_ptr;}private:T*_ptr;};看似机智实则埋雷拷贝或赋值后源对象内部的裸指针变成了nullptr。如果你继续使用原来的对象就会访问空指针导致未定义行为。pz::auto_ptrints(newint(1));pz::auto_ptrints1(s);// s 被置空pz::auto_ptrints2;s2s1;// s1 被置空// std::cout *s; // 危险s 已是空指针这种“拷贝即转移”的语义违背了人们对拷贝的直觉。因此auto_ptr在 C11 中被标记为废弃C17 中被彻底移除。它的继任者就是unique_ptr。3. unique_ptr独占资源的正确姿势unique_ptr明确表达了独占所有权同一时刻只有一个unique_ptr管理一段资源。它直接禁止了拷贝操作但允许通过移动语义转移所有权语义清晰且安全。3.1 手写实现与接口templateclassTclassunique_ptr{public:// 构造函数explicit 禁止隐式转换explicitunique_ptr(T*ptrnullptr):_ptr(ptr){}// 禁止拷贝unique_ptr(constunique_ptrTptr)delete;unique_ptrToperator(constunique_ptrTptr)delete;// 移动赋值通过交换转移所有权unique_ptrToperator(unique_ptrTptr){std::swap(ptr._ptr,_ptr);return*this;}~unique_ptr(){delete_ptr;}// 常用接口Toperator*(){return*_ptr;}T*operator-(){return_ptr;}T*get()const{return_ptr;}// 获取内部裸指针explicitoperatorbool()const{// 判空安全布尔转换return_ptr!nullptr;}private:T*_ptr;};常用接口说明get()返回管理的裸指针不转移所有权。operator*和operator-像指针一样访问对象。explicit operator bool()可在条件语句中安全使用如if (ptr)。标准库还提供reset(ptr)替换管理的对象release()放弃所有权并返回裸指针。3.2 赋值行为unique_ptr的赋值只有移动赋值一种形式pz::unique_ptrints(newint(1));pz::unique_ptrints1;s1std::move(s);// s 将所有权转移给 s1s 变为空// s1 接管原 s 的资源s1 原来的资源如果有会被正确释放移动赋值时通常先释放自身持有的资源再接管新资源。代码中采用std::swap将资源交换也是一种常见手法源对象的资源最终会在其析构时安全释放。3.3 删除器模板参数方式unique_ptr允许自定义删除器但删除器的类型是模板参数的一部分templateclassT,classDeleterstd::default_deleteTclassunique_ptr;这意味着如果你想用自定义删除器必须在类型中明确给出// unique_ptr: 删除器类型在模板参数std::unique_ptrFILE,decltype(fclose)fp(fopen(test.txt,r),fclose);优缺点零额外开销空基类优化或直接存储删除器但不同的删除器会形成不同的类型不能直接混用。我们的简化版没有实现模板参数删除器标准库的实现依赖模板参数和 EBO 优化。4. shared_ptr共享所有权与引用计数当多个对象需要共享同一资源时就需要shared_ptr。它通过引用计数来跟踪有多少个shared_ptr指向同一资源当最后一个shared_ptr销毁时自动释放资源。4.1 手写实现与接口templateclassTclassshared_ptr{public:shared_ptr(T*ptrnullptr):_ptr(ptr),_n(newint(1)){}shared_ptr(constshared_ptrTptr):_ptr(ptr._ptr),_n(ptr._n){(*_n);}// 带自定义删除器的构造函数删除器在参数中templateclassUshared_ptr(T*ptr,U del):_ptr(ptr),_n(newint(1)),_del(del){}shared_ptroperator(constshared_ptrx){if(x._ptr!_ptr){release();_ptrx._ptr;_nx._n;_delx._del;(*_n);}return*this;}~shared_ptr(){release();}// 常用接口Toperator*(){return*_ptr;}T*operator-(){return_ptr;}T*get()const{return_ptr;}intuse_count()const{return*_n;}// 返回当前引用计数explicitoperatorbool()const{return_ptr!nullptr;}private:voidrelease(){if(--(*_n)0){delete_n;_del(_ptr);_nnullptr;_ptrnullptr;}}T*_ptr;int*_n;// 堆上的引用计数std::functionvoid(T*)_del[](T*a){deletea;};// 删除器};常用接口补充use_count()返回当前共享资源的shared_ptr数量调试时有用但不建议用于逻辑判断多线程下可能瞬息改变。unique()C17 废弃C20 移除判断是否是唯一所有者等价于use_count() 1。reset()释放当前所有权可选地接管新指针。get()与operator bool同上。4.2 赋值行为shared_ptr的赋值是拷贝赋值会正确地调整引用计数pz::shared_ptrintp1(newint(10));pz::shared_ptrintp2(newint(20));p2p1;// p2 原来指向的资源计数减1若归零则释放// p1 的引用计数加1p2 与 p1 共享同一资源移动赋值则直接转移所有权源对象计数不变目标对象原资源计数减1p2std::move(p1);// p1 变为空p2 接管 p1 资源p2 原资源释放4.3 删除器构造函数参数方式shared_ptr的删除器是通过构造函数参数传递的它会进行类型擦除通常使用std::function或类似技术因此删除器类型不影响shared_ptr的签名// shared_ptr: 删除器在构造函数参数中std::shared_ptrFILEfp(fopen(test.txt,r),fclose);// 不同类型的删除器只要签名兼容可以赋值给同一个 shared_ptr 对象std::shared_ptrFILEfp2;fp2fp;// 正确删除器被共享优点灵活不同删除器的shared_ptr类型相同可以互相赋值。代价是删除器可能带来额外的存储和间接调用开销现代实现已高度优化使用小对象优化等手段。我们的简化版用std::functionvoid(T*)模拟了这种类型擦除行为实际标准库的实现更精巧但不影响理解原理。4.4 循环引用shared_ptr 的致命陷阱假设我们有一个双向链表节点每个节点通过shared_ptr指向前后节点structListNode{int_data;std::shared_ptrListNode_next;std::shared_ptrListNode_prev;~ListNode(){std::cout~ListNode()std::endl;}};std::shared_ptrListNoden1(newListNode);std::shared_ptrListNoden2(newListNode);n1-_nextn2;n2-_prevn1;// 循环引用此时n1和n2的引用计数都是 2外部各有一个shared_ptr内部通过_next/_prev互相指向。当离开作用域时外部的n1、n2销毁各自计数减为 1。但内部互相指向的shared_ptr仍然存在所以资源永远不会被释放析构函数不会被调用内存泄漏。这就是著名的“循环引用”问题。5. weak_ptr打破循环的“旁观者”为了解决循环引用C 引入了weak_ptr。它不增加引用计数只是“观察”一个shared_ptr管理的资源不参与资源生命周期的管理。当最后一个shared_ptr销毁时即使还有weak_ptr存在资源也会被释放weak_ptr会变为“失效”状态。我们的简化版pz::weak_ptr仅存储裸指针不管理引用计数templateclassTclassweak_ptr{public:weak_ptr()default;weak_ptr(constshared_ptrTx):_ptr(x.get()){}weak_ptroperator(constshared_ptrTx){_ptrx.get();return*this;}private:T*_ptrnullptr;};虽然这个版本省略了标准weak_ptr的lock()和expired()等接口但足以说明核心思想它仅仅“指向”资源而不影响引用计数。在上面的链表例子中若将_next和_prev改为weak_ptr循环引用就被打破了structlist{intdata;pz::weak_ptrlistnext;pz::weak_ptrlistpro;// 使用 weak_ptr 不增加引用计数~list(){std::cout~list()std::endl;}};pz::shared_ptrlistli1(newlist);pz::shared_ptrlistli2(newlist);li2-nextli1;// weak_ptr 从 shared_ptr 构造不增加计数li1-proli2;// 离开作用域li1, li2 的计数都减为0资源正常释放此时外部shared_ptr销毁时引用计数归零内部节点间的weak_ptr无法阻止资源释放循环被安全打破。6. 用智能指针安全管理动态数组在之前的实现中我们用new/delete管理单个对象。当需要管理动态数组new T[n]时智能指针的使用方式有重要区别若用错会导致未定义行为和内存泄漏。6.1unique_ptr管理数组C11 起支持std::unique_ptr提供了数组偏特化版本std::unique_ptrT[]它内部使用delete[]并提供operator[]访问元素但不支持operator*和operator-。#includememorystructlist{intdata;};std::unique_ptrlist[]arr(newlist[10]);// 正确析构时调用 delete[]arr[0].data5;// 通过 operator[] 访问元素注意写成std::unique_ptrlist管理数组是错误的默认删除器会调用delete而不是delete[]导致资源泄漏。不需要手动传入删除器标准库已经专门处理。6.2shared_ptr管理数组C11/14 与 C17 的区别shared_ptr在 C11/14 中没有数组偏特化管理数组必须在构造函数中传入自定义删除器// C11/14 正确写法传入删除器std::shared_ptrlistsp(newlist[10],[](list*p){delete[]p;});// 或使用标准删除器std::shared_ptrlistsp2(newlist[10],std::default_deletelist[]());如果直接写成std::shared_ptrlist sp(new list[10]);默认会用delete释放导致未定义行为。从C17开始shared_ptr也支持数组偏特化std::shared_ptrT[]此时可以自动调用delete[]。6.3 自定义删除器传递方式回顾智能指针删除器传递方式数组管理建议unique_ptr删除器类型作为模板参数unique_ptrT, Deleter直接用unique_ptrT[]无需手动传入删除器shared_ptr删除器作为构造函数参数shared_ptrT(ptr, deleter)C11/14 必须手动传入delete[]删除器C17 起可用shared_ptrT[]6.4 最佳实践若只有单一所有者优先用std::unique_ptr管理数组时写成std::unique_ptrT[]。若需要共享所有权才用std::shared_ptr并在 C11/14 中必须显式传递数组删除器lambda 或std::default_deleteT[]()。尽量不要用原生new[]/delete[]让智能指针接管既避免泄漏也保障异常安全。将这些细节补充到你手写的unique_ptr和shared_ptr实现旁就能形成一份完整的“动态内存管理避坑指南”。7. 总结智能指针选择指南类型所有权拷贝删除器使用场景auto_ptr独占转移式允许会置空源不支持已废弃不要使用unique_ptr独占移动式禁止拷贝允许移动模板参数零开销明确的单一所有者替代裸指针自定义删除器shared_ptr共享引用计数式拷贝构造函数参数类型擦除多个所有者共享资源weak_ptr不拥有从shared_ptr构造—打破循环引用观察者模式核心接口速查共同接口*,-,get(),operator boolunique_ptr额外release(),reset(), 移动构造/赋值shared_ptr额外use_count(),unique(),reset(), 拷贝构造/赋值weak_ptr额外lock()提升为shared_ptrexpired()检查有效性删除器传递差异总结unique_ptrT, Deleter删除器类型作为模板参数零开销但影响类型。shared_ptrT删除器在构造函数中传入类型擦除同一类型可共享更灵活。记住这些类掌握了 C 智能指针的精髓用对象管理资源让析构函数为你保驾护航再也不用担心“忘记 delete”。现代 C 几乎不应再出现裸new和delete拥抱智能指针资源泄漏就会远离你。
智能指针:从泄漏到安全的内存管理
发布时间:2026/6/4 3:34:07
# 智能指针深度剖析从资源泄漏到循环引用手写 auto_ptr、unique_ptr、shared_ptr、weak_ptr 管理动态内存一直是 C 程序员的噩梦。忘记 delete 会导致内存泄漏异常抛出会让代码跳过清理逻辑对象所有权不清晰更是万恶之源。C 的解决之道就是**智能指针**——它们是 RAII 思想的完美体现在构造函数中获取资源在析构函数中释放资源让资源生命周期与对象生命周期严格绑定。 本文将用**手写的简化版智能指针**先讲清楚资源泄漏与内存泄漏的概念再一步步讲解 auto_ptr、unique_ptr、shared_ptr、weak_ptr 的实现原理以及它们常用接口、赋值行为和删除器传递方式。所有代码都可以编译运行建议你亲自调试一遍加深理解。 ## 1. 资源泄漏与内存泄漏 在讨论智能指针之前必须先明确两个概念 ### 1.1 内存泄漏 **内存泄漏**指程序在堆上分配了内存但在使用完毕后没有释放导致该内存无法被再次利用。随着程序运行泄漏的内存不断累积最终可能耗尽系统内存。 cpp void leak() { int* p new int(42); // 分配内存 // 忘记 delete p // 内存泄漏 }如果函数内发生异常情况更糟voiddanger(){int*pnewint(42);throwstd::runtime_error(oops);deletep;// 永远执行不到内存泄漏}1.2 资源泄漏广义资源泄漏是更广泛的概念指任何需要手动释放的系统资源未能正确释放包括但不限于动态分配的内存new/malloc未对应delete/free文件句柄fopen未fclose互斥锁mutex.lock()未unlock()网络套接字、数据库连接等内存泄漏是资源泄漏中最常见的一种。C 的 RAII 思想正是为了解决所有资源泄漏而设计的把资源生命周期绑定到局部对象上利用析构函数的自动调用特性保证资源释放。智能指针是 RAII 在动态内存管理上的直接应用。2. auto_ptr充满陷阱的“半成品”C98 提供了std::auto_ptr它尝试用“所有权转移”来管理动态内存。我们手写的pz::auto_ptr复刻了它的核心行为templateclassTclassauto_ptr{public:auto_ptr(T*ptrnullptr):_ptr(ptr){}auto_ptr(auto_ptrTptr):_ptr(ptr._ptr){ptr._ptrnullptr;// 拷贝构造时“窃取”源对象资源}auto_ptrToperator(auto_ptrTptr){if(ptr._ptr){_ptrptr._ptr;ptr._ptrnullptr;// 赋值时也“窃取”资源}return*this;}~auto_ptr(){delete_ptr;}Toperator*(){return*_ptr;}T*operator-(){return_ptr;}private:T*_ptr;};看似机智实则埋雷拷贝或赋值后源对象内部的裸指针变成了nullptr。如果你继续使用原来的对象就会访问空指针导致未定义行为。pz::auto_ptrints(newint(1));pz::auto_ptrints1(s);// s 被置空pz::auto_ptrints2;s2s1;// s1 被置空// std::cout *s; // 危险s 已是空指针这种“拷贝即转移”的语义违背了人们对拷贝的直觉。因此auto_ptr在 C11 中被标记为废弃C17 中被彻底移除。它的继任者就是unique_ptr。3. unique_ptr独占资源的正确姿势unique_ptr明确表达了独占所有权同一时刻只有一个unique_ptr管理一段资源。它直接禁止了拷贝操作但允许通过移动语义转移所有权语义清晰且安全。3.1 手写实现与接口templateclassTclassunique_ptr{public:// 构造函数explicit 禁止隐式转换explicitunique_ptr(T*ptrnullptr):_ptr(ptr){}// 禁止拷贝unique_ptr(constunique_ptrTptr)delete;unique_ptrToperator(constunique_ptrTptr)delete;// 移动赋值通过交换转移所有权unique_ptrToperator(unique_ptrTptr){std::swap(ptr._ptr,_ptr);return*this;}~unique_ptr(){delete_ptr;}// 常用接口Toperator*(){return*_ptr;}T*operator-(){return_ptr;}T*get()const{return_ptr;}// 获取内部裸指针explicitoperatorbool()const{// 判空安全布尔转换return_ptr!nullptr;}private:T*_ptr;};常用接口说明get()返回管理的裸指针不转移所有权。operator*和operator-像指针一样访问对象。explicit operator bool()可在条件语句中安全使用如if (ptr)。标准库还提供reset(ptr)替换管理的对象release()放弃所有权并返回裸指针。3.2 赋值行为unique_ptr的赋值只有移动赋值一种形式pz::unique_ptrints(newint(1));pz::unique_ptrints1;s1std::move(s);// s 将所有权转移给 s1s 变为空// s1 接管原 s 的资源s1 原来的资源如果有会被正确释放移动赋值时通常先释放自身持有的资源再接管新资源。代码中采用std::swap将资源交换也是一种常见手法源对象的资源最终会在其析构时安全释放。3.3 删除器模板参数方式unique_ptr允许自定义删除器但删除器的类型是模板参数的一部分templateclassT,classDeleterstd::default_deleteTclassunique_ptr;这意味着如果你想用自定义删除器必须在类型中明确给出// unique_ptr: 删除器类型在模板参数std::unique_ptrFILE,decltype(fclose)fp(fopen(test.txt,r),fclose);优缺点零额外开销空基类优化或直接存储删除器但不同的删除器会形成不同的类型不能直接混用。我们的简化版没有实现模板参数删除器标准库的实现依赖模板参数和 EBO 优化。4. shared_ptr共享所有权与引用计数当多个对象需要共享同一资源时就需要shared_ptr。它通过引用计数来跟踪有多少个shared_ptr指向同一资源当最后一个shared_ptr销毁时自动释放资源。4.1 手写实现与接口templateclassTclassshared_ptr{public:shared_ptr(T*ptrnullptr):_ptr(ptr),_n(newint(1)){}shared_ptr(constshared_ptrTptr):_ptr(ptr._ptr),_n(ptr._n){(*_n);}// 带自定义删除器的构造函数删除器在参数中templateclassUshared_ptr(T*ptr,U del):_ptr(ptr),_n(newint(1)),_del(del){}shared_ptroperator(constshared_ptrx){if(x._ptr!_ptr){release();_ptrx._ptr;_nx._n;_delx._del;(*_n);}return*this;}~shared_ptr(){release();}// 常用接口Toperator*(){return*_ptr;}T*operator-(){return_ptr;}T*get()const{return_ptr;}intuse_count()const{return*_n;}// 返回当前引用计数explicitoperatorbool()const{return_ptr!nullptr;}private:voidrelease(){if(--(*_n)0){delete_n;_del(_ptr);_nnullptr;_ptrnullptr;}}T*_ptr;int*_n;// 堆上的引用计数std::functionvoid(T*)_del[](T*a){deletea;};// 删除器};常用接口补充use_count()返回当前共享资源的shared_ptr数量调试时有用但不建议用于逻辑判断多线程下可能瞬息改变。unique()C17 废弃C20 移除判断是否是唯一所有者等价于use_count() 1。reset()释放当前所有权可选地接管新指针。get()与operator bool同上。4.2 赋值行为shared_ptr的赋值是拷贝赋值会正确地调整引用计数pz::shared_ptrintp1(newint(10));pz::shared_ptrintp2(newint(20));p2p1;// p2 原来指向的资源计数减1若归零则释放// p1 的引用计数加1p2 与 p1 共享同一资源移动赋值则直接转移所有权源对象计数不变目标对象原资源计数减1p2std::move(p1);// p1 变为空p2 接管 p1 资源p2 原资源释放4.3 删除器构造函数参数方式shared_ptr的删除器是通过构造函数参数传递的它会进行类型擦除通常使用std::function或类似技术因此删除器类型不影响shared_ptr的签名// shared_ptr: 删除器在构造函数参数中std::shared_ptrFILEfp(fopen(test.txt,r),fclose);// 不同类型的删除器只要签名兼容可以赋值给同一个 shared_ptr 对象std::shared_ptrFILEfp2;fp2fp;// 正确删除器被共享优点灵活不同删除器的shared_ptr类型相同可以互相赋值。代价是删除器可能带来额外的存储和间接调用开销现代实现已高度优化使用小对象优化等手段。我们的简化版用std::functionvoid(T*)模拟了这种类型擦除行为实际标准库的实现更精巧但不影响理解原理。4.4 循环引用shared_ptr 的致命陷阱假设我们有一个双向链表节点每个节点通过shared_ptr指向前后节点structListNode{int_data;std::shared_ptrListNode_next;std::shared_ptrListNode_prev;~ListNode(){std::cout~ListNode()std::endl;}};std::shared_ptrListNoden1(newListNode);std::shared_ptrListNoden2(newListNode);n1-_nextn2;n2-_prevn1;// 循环引用此时n1和n2的引用计数都是 2外部各有一个shared_ptr内部通过_next/_prev互相指向。当离开作用域时外部的n1、n2销毁各自计数减为 1。但内部互相指向的shared_ptr仍然存在所以资源永远不会被释放析构函数不会被调用内存泄漏。这就是著名的“循环引用”问题。5. weak_ptr打破循环的“旁观者”为了解决循环引用C 引入了weak_ptr。它不增加引用计数只是“观察”一个shared_ptr管理的资源不参与资源生命周期的管理。当最后一个shared_ptr销毁时即使还有weak_ptr存在资源也会被释放weak_ptr会变为“失效”状态。我们的简化版pz::weak_ptr仅存储裸指针不管理引用计数templateclassTclassweak_ptr{public:weak_ptr()default;weak_ptr(constshared_ptrTx):_ptr(x.get()){}weak_ptroperator(constshared_ptrTx){_ptrx.get();return*this;}private:T*_ptrnullptr;};虽然这个版本省略了标准weak_ptr的lock()和expired()等接口但足以说明核心思想它仅仅“指向”资源而不影响引用计数。在上面的链表例子中若将_next和_prev改为weak_ptr循环引用就被打破了structlist{intdata;pz::weak_ptrlistnext;pz::weak_ptrlistpro;// 使用 weak_ptr 不增加引用计数~list(){std::cout~list()std::endl;}};pz::shared_ptrlistli1(newlist);pz::shared_ptrlistli2(newlist);li2-nextli1;// weak_ptr 从 shared_ptr 构造不增加计数li1-proli2;// 离开作用域li1, li2 的计数都减为0资源正常释放此时外部shared_ptr销毁时引用计数归零内部节点间的weak_ptr无法阻止资源释放循环被安全打破。6. 用智能指针安全管理动态数组在之前的实现中我们用new/delete管理单个对象。当需要管理动态数组new T[n]时智能指针的使用方式有重要区别若用错会导致未定义行为和内存泄漏。6.1unique_ptr管理数组C11 起支持std::unique_ptr提供了数组偏特化版本std::unique_ptrT[]它内部使用delete[]并提供operator[]访问元素但不支持operator*和operator-。#includememorystructlist{intdata;};std::unique_ptrlist[]arr(newlist[10]);// 正确析构时调用 delete[]arr[0].data5;// 通过 operator[] 访问元素注意写成std::unique_ptrlist管理数组是错误的默认删除器会调用delete而不是delete[]导致资源泄漏。不需要手动传入删除器标准库已经专门处理。6.2shared_ptr管理数组C11/14 与 C17 的区别shared_ptr在 C11/14 中没有数组偏特化管理数组必须在构造函数中传入自定义删除器// C11/14 正确写法传入删除器std::shared_ptrlistsp(newlist[10],[](list*p){delete[]p;});// 或使用标准删除器std::shared_ptrlistsp2(newlist[10],std::default_deletelist[]());如果直接写成std::shared_ptrlist sp(new list[10]);默认会用delete释放导致未定义行为。从C17开始shared_ptr也支持数组偏特化std::shared_ptrT[]此时可以自动调用delete[]。6.3 自定义删除器传递方式回顾智能指针删除器传递方式数组管理建议unique_ptr删除器类型作为模板参数unique_ptrT, Deleter直接用unique_ptrT[]无需手动传入删除器shared_ptr删除器作为构造函数参数shared_ptrT(ptr, deleter)C11/14 必须手动传入delete[]删除器C17 起可用shared_ptrT[]6.4 最佳实践若只有单一所有者优先用std::unique_ptr管理数组时写成std::unique_ptrT[]。若需要共享所有权才用std::shared_ptr并在 C11/14 中必须显式传递数组删除器lambda 或std::default_deleteT[]()。尽量不要用原生new[]/delete[]让智能指针接管既避免泄漏也保障异常安全。将这些细节补充到你手写的unique_ptr和shared_ptr实现旁就能形成一份完整的“动态内存管理避坑指南”。7. 总结智能指针选择指南类型所有权拷贝删除器使用场景auto_ptr独占转移式允许会置空源不支持已废弃不要使用unique_ptr独占移动式禁止拷贝允许移动模板参数零开销明确的单一所有者替代裸指针自定义删除器shared_ptr共享引用计数式拷贝构造函数参数类型擦除多个所有者共享资源weak_ptr不拥有从shared_ptr构造—打破循环引用观察者模式核心接口速查共同接口*,-,get(),operator boolunique_ptr额外release(),reset(), 移动构造/赋值shared_ptr额外use_count(),unique(),reset(), 拷贝构造/赋值weak_ptr额外lock()提升为shared_ptrexpired()检查有效性删除器传递差异总结unique_ptrT, Deleter删除器类型作为模板参数零开销但影响类型。shared_ptrT删除器在构造函数中传入类型擦除同一类型可共享更灵活。记住这些类掌握了 C 智能指针的精髓用对象管理资源让析构函数为你保驾护航再也不用担心“忘记 delete”。现代 C 几乎不应再出现裸new和delete拥抱智能指针资源泄漏就会远离你。