
1. 项目概述为什么我们需要智能指针在C的世界里内存管理就像一场没有硝烟的战争。你亲手用new申请了一块内存就必须在某个地方用delete把它还回去否则就会导致内存泄漏——程序像一只不断吃内存的怪兽最终耗尽系统资源而崩溃。更棘手的是如果你在多个地方持有同一个裸指针谁该在什么时候负责删除它一个不小心就可能出现“双重释放”double free或者“悬空指针”dangling pointer的灾难。我刚入行时没少在这上面栽跟头。一个复杂的业务逻辑里指针传来传去到最后自己都忘了这块内存到底释放了没有只能靠调试器一点点去查效率极低。后来接触到RAIIResource Acquisition Is Initialization资源获取即初始化这个理念才算是找到了救星。它的核心思想很简单对象的生命周期绑定资源的持有周期。构造函数里获取资源比如分配内存析构函数里自动释放资源。这样只要对象出了作用域资源就一定会被清理完全不用程序员手动操心。智能指针就是RAII理念在内存管理领域最经典、最实用的实现。它把裸指针包装成一个类对象通过重载运算符如*,-让我们像使用普通指针一样使用它同时利用C的析构函数自动调用机制在智能指针对象销毁时自动释放其管理的内存。这不仅仅是语法糖它是一种编程范式的转变从“手动挡”升级到了“自动挡”。今天我们就来彻底拆解C标准库中的三大智能指针unique_ptr,shared_ptr和weak_ptr。我会结合五个可以直接编译运行的示例带你从裸指针的泥潭优雅地演进到RAII的康庄大道。无论你是正在被内存问题困扰的C新手还是想深化理解的老手这篇文章都能给你带来实实在在的收获。2. 核心智能指针详解与选型指南C11标准引入了现代智能指针它们位于memory头文件中彻底改变了我们管理动态内存的方式。理解它们各自的所有权ownership语义是正确选型和使用的关键。2.1std::unique_ptr独占所有权的守卫unique_ptr如其名独占它所指向对象的所有权。同一时刻只能有一个unique_ptr指向一个给定的对象。当这个unique_ptr被销毁例如离开作用域它所管理的对象也会被自动销毁。核心特性独占所有权无法进行拷贝构造和拷贝赋值。这是通过将拷贝构造函数和拷贝赋值运算符设置为 delete实现的。移动语义所有权可以通过移动构造函数或移动赋值运算符进行转移。转移后源unique_ptr变为空指针。自定义删除器可以指定一个函数或可调用对象在释放内存时执行特定的清理操作如关闭文件、释放锁等这大大扩展了其应用范围不限于内存。为什么选择unique_ptr这是你应该首先考虑的默认选择。它的开销极小通常与裸指针无异取决于删除器因为它不需要维护引用计数等额外数据。它清晰地表达了“这个指针唯一地拥有这个对象”的意图避免了所有权混淆。在函数传递、作为类成员等场景中如果所有权是明确的、唯一的就用unique_ptr。2.2std::shared_ptr共享所有权的合作者当多个实体需要“共享”同一个对象并且无法确定哪个实体最后使用它时shared_ptr就派上用场了。它通过引用计数reference counting机制来实现共享所有权。核心机制每个shared_ptr对象内部除了存储裸指针还关联着一个控制块control block其中至少包含两个引用计数器强引用计数use_count记录有多少个shared_ptr共享着对象的所有权。当一个新的shared_ptr通过拷贝指向同一对象时计数加1当任何一个shared_ptr被销毁或重置时计数减1。当强引用计数降为0时管理的内存被释放。弱引用计数weak_count记录有多少个weak_ptr观察着该对象。这个计数不影响对象的生命周期。为什么选择shared_ptr只有在所有权确实需要被共享时才使用它。典型的场景包括缓存系统中的对象多个客户端可能同时持有其引用。复杂的图结构或树结构中节点可能需要共享子节点虽然需注意循环引用问题。需要将对象存入标准容器如vectorshared_ptrT并且容器内外都需要访问该对象。注意shared_ptr不是免费的。控制块的内存分配、原子操作带来的线程安全开销引用计数的增减是原子的都是成本。滥用shared_ptr会导致性能下降和逻辑复杂化。2.3std::weak_ptr打破循环引用的观察者weak_ptr是为了解决shared_ptr的循环引用问题而生的。它指向一个由shared_ptr管理的对象但不增加其强引用计数。也就是说weak_ptr的生存期不影响其所指对象的生存期。核心用途打破循环引用这是其最主要的功能。如果两个对象各自持有一个指向对方的shared_ptr就会形成循环引用导致引用计数永远无法归零内存泄漏。将其中一方改为持有weak_ptr即可打破循环。临时访问共享对象当你需要访问一个可能已被释放的共享对象时可以使用weak_ptr。它需要通过lock()成员函数尝试获取一个临时的shared_ptr。如果对象还存在lock()返回一个有效的shared_ptr此时强引用计数会增加如果对象已被释放则返回一个空的shared_ptr。这是一种安全的访问方式。如何使用weak_ptr你不能直接解引用weak_ptr。必须先用lock()方法将其“升级”为shared_ptr。std::weak_ptrMyClass wp someSharedPtr; if (auto sp wp.lock()) { // 尝试获取 shared_ptr // 对象还存在可以安全使用 sp sp-doSomething(); } else { // 对象已被释放 std::cout Object is gone.\n; }2.4 智能指针选型速查表为了更直观地对比和选择我将三大智能指针的核心区别整理成下表特性std::unique_ptrTstd::shared_ptrTstd::weak_ptrT所有权语义独占所有权共享所有权无所有权弱引用拷贝不允许允许增加引用计数允许不影响强引用计数移动允许转移所有权允许转移所有权引用计数不变允许是否影响对象生命周期是独占决定是引用计数归零则释放否性能开销极小通常无额外开销较大控制块内存、原子操作与shared_ptr控制块相关典型使用场景工厂模式返回值、独占类成员、资源管理缓存、共享配置、复杂数据结构需防循环引用打破shared_ptr循环引用、观察者模式如何获取裸指针get()成员函数get()成员函数必须先通过lock()转为shared_ptr自定义删除器支持是类型的一部分支持不是类型的一部分继承自关联的shared_ptr选型心法默认用unique_ptr除非有明确的共享需求否则优先使用它。它最安全、最高效。慎用shared_ptr仅在所有权需要被多个独立实体共享且它们的生命周期不确定时使用。要时刻警惕循环引用。用weak_ptr解决循环引用当使用shared_ptr出现循环引用时将其中不需要维持对象生命周期的引用改为weak_ptr。3. 从裸指针到智能指针5个可运行示例解析理论说再多不如代码跑一遍。下面这5个示例我精心设计了从易到难、覆盖常见场景的案例每个都可以独立编译运行需要C11及以上编译器如g -stdc11 -o example example.cpp。我不仅会展示代码更会解释为什么要这么写以及实践中容易踩的坑。3.1 示例一unique_ptr基础用法与所有权转移这个例子展示了unique_ptr的创建、基本使用以及最重要的特性——所有权的移动。#include iostream #include memory class Resource { public: Resource() { std::cout Resource acquired.\n; } ~Resource() { std::cout Resource destroyed.\n; } void doSomething() { std::cout Resource is doing something.\n; } }; int main() { std::cout Example 1: unique_ptr Basics \n; // 1. 创建 unique_ptr (推荐使用 std::make_unique, C14起) // auto 关键字让代码更简洁 auto res1 std::make_uniqueResource(); // 2. 像普通指针一样使用 res1-doSomething(); (*res1).doSomething(); // 等价于上一行 // 3. 所有权转移从 res1 移动到 res2 std::cout Before move, res1 is (res1 ? not null : null) std::endl; auto res2 std::move(res1); // std::move 将 res1 转为右值触发移动构造 std::cout After move:\n; std::cout res1 is (res1 ? not null : null) std::endl; std::cout res2 is (res2 ? not null : null) std::endl; // 4. 尝试拷贝 (编译错误) // auto res3 res2; // Error: Call to deleted constructor of ‘std::unique_ptrResource’ // 5. 显式释放资源并重置指针 (通常不需要手动做这里仅为演示) res2.reset(); // 调用 Resource 的析构函数res2 变为 nullptr std::cout After reset, res2 is (res2 ? not null : null) std::endl; // 6. 离开作用域res1 (已是nullptr) 和 res2 (已是nullptr) 自动销毁无资源泄漏 std::cout Leaving main scope...\n; return 0; }运行结果分析 Example 1: unique_ptr Basics Resource acquired. Resource is doing something. Resource is doing something. Before move, res1 is not null After move: res1 is null res2 is not null Resource destroyed. After reset, res2 is null Leaving main scope...可以看到资源在res2.reset()时就被立即销毁了而不是等到main函数结束。这证明了unique_ptr的生命周期管理是精确的。实操心得优先使用std::make_unique这是C14引入的它比直接new更安全、更高效。安全体现在它将对象构造和智能指针构造合并为一个步骤避免了因异常导致的内存泄漏虽然本例简单但在复杂表达式里很重要。高效体现在它只需要一次内存分配new创建对象 unique_ptr控制结构而unique_ptrT(new T(...))可能需要两次。std::move是转移所有权的信号看到std::move你就应该意识到所有权即将被转移原来的指针会失效。这是代码意图的清晰表达。不要尝试获取并持有unique_ptr内部裸指针res.get()返回的裸指针只应在当前作用域内临时使用绝不能用来创建另一个智能指针或手动delete否则会导致双重释放。3.2 示例二shared_ptr与引用计数实战这个例子演示了shared_ptr的共享特性以及引用计数是如何工作的。#include iostream #include memory class Observer { public: void notify(const std::string msg) { std::cout Observer received: msg std::endl; } }; int main() { std::cout \n Example 2: shared_ptr and Reference Counting \n; // 1. 创建 shared_ptr (推荐使用 std::make_shared) // make_shared 通常更高效因为它将对象和控制块分配在连续内存中。 auto sp1 std::make_sharedObserver(); std::cout sp1 use_count: sp1.use_count() std::endl; // 输出: 1 // 2. 拷贝构造共享所有权 auto sp2 sp1; // 拷贝引用计数1 std::cout After sp2 sp1:\n; std::cout sp1 use_count: sp1.use_count() std::endl; // 输出: 2 std::cout sp2 use_count: sp2.use_count() std::endl; // 输出: 2 // 3. 通过 sp1 和 sp2 操作的是同一个对象 sp1-notify(Hello from sp1); sp2-notify(Hello from sp2); // 4. 重置 sp1放弃所有权 sp1.reset(); std::cout After sp1.reset():\n; std::cout sp1 is (sp1 ? not null : null) std::endl; std::cout sp2 use_count: sp2.use_count() std::endl; // 输出: 1 // 5. sp2 仍然持有对象可以正常使用 if(sp2) { sp2-notify(sp2 is still alive); } // 6. sp2 离开作用域引用计数归零对象自动销毁 std::cout sp2 is about to go out of scope...\n; return 0; // 此处 Observer 对象被销毁 }运行结果分析 Example 2: shared_ptr and Reference Counting sp1 use_count: 1 After sp2 sp1: sp1 use_count: 2 sp2 use_count: 2 Observer received: Hello from sp1 Observer received: Hello from sp2 After sp1.reset(): sp1 is null sp2 use_count: 1 Observer received: sp2 is still alive sp2 is about to go out of scope...通过use_count()的输出我们可以清晰地看到引用计数的变化。sp1.reset()后计数减为1对象并未销毁直到sp2也离开作用域。注意事项避免从裸指针创建多个独立的shared_ptr这是灾难性的错误。Observer* rawPtr new Observer(); std::shared_ptrObserver spA(rawPtr); std::shared_ptrObserver spB(rawPtr); // 错误spA和spB会有独立的控制块会导致双重释放。永远使用make_shared或者将一个new表达式的结果直接传递给shared_ptr的构造函数。use_count()通常只用于调试在生产代码中依赖use_count()的值来做逻辑判断通常是设计有问题的标志因为多线程环境下它可能瞬间变化。3.3 示例三循环引用问题与weak_ptr解决方案这是shared_ptr最经典的陷阱也是weak_ptr大显身手的地方。#include iostream #include memory class Node { public: std::string name; // 关键点这里使用 shared_ptr 会导致循环引用 // std::shared_ptrNode partner; // 解决方案使用 weak_ptr std::weak_ptrNode partner; // 改为 weak_ptr Node(const std::string n) : name(n) { std::cout Node name constructed.\n; } ~Node() { std::cout Node name destroyed.\n; } void setPartner(std::shared_ptrNode p) { partner p; // weak_ptr 可以从 shared_ptr 赋值 } void checkPartner() { // 使用前必须尝试提升为 shared_ptr if (auto p partner.lock()) { std::cout name s partner is p-name (alive)\n; } else { std::cout name s partner is gone.\n; } } }; int main() { std::cout \n Example 3: Breaking Circular Reference with weak_ptr \n; { auto alice std::make_sharedNode(Alice); auto bob std::make_sharedNode(Bob); alice-setPartner(bob); bob-setPartner(alice); std::cout Alice use_count: alice.use_count() std::endl; // 输出: 1 (Bob的partner是weak_ptr) std::cout Bob use_count: bob.use_count() std::endl; // 输出: 1 alice-checkPartner(); bob-checkPartner(); } // alice 和 bob 离开作用域引用计数都归零对象被正确销毁。 std::cout Both nodes should be destroyed by now.\n; return 0; }运行结果使用weak_ptr后 Example 3: Breaking Circular Reference with weak_ptr Node Alice constructed. Node Bob constructed. Alice use_count: 1 Bob use_count: 1 Alices partner is Bob (alive) Bobs partner is Alice (alive) Node Bob destroyed. Node Alice destroyed. Both nodes should be destroyed by now.可以看到两个Node对象都被正确析构了。如果将代码中的weak_ptr改回shared_ptr你会发现析构函数永远不会被调用因为alice和bob互相持有对方的shared_ptr引用计数永远为1导致内存泄漏。核心要点循环引用是逻辑问题weak_ptr是技术解决方案首先要审视设计是否真的需要双向的强引用。很多时候单向引用或重新设计依赖关系可以避免循环。当循环引用不可避免时如本例的“伙伴”关系weak_ptr是标准答案。lock()是线程安全的weak_ptr::lock()操作是原子的它保证了在判断对象是否存在和增加引用计数如果存在的过程中对象不会被其他线程销毁。返回的shared_ptr则保证了在接下来的使用中对象存活。3.4 示例四自定义删除器的高级用法智能指针的强大之处在于它不仅能管理new分配的内存还能管理任何需要“释放”的资源比如文件句柄、网络套接字、互斥锁等。这是通过自定义删除器实现的。#include iostream #include memory #include cstdio // 用于 std::FILE, fopen, fclose // 1. 自定义删除器函数指针形式 void FileDeleter(std::FILE* fp) { if (fp) { std::fclose(fp); std::cout File closed via function deleter.\n; } } // 2. 自定义删除器函数对象仿函数形式 struct ArrayDeleter { void operator()(int* p) const { delete[] p; // 正确释放数组 std::cout int[] array deleted via functor.\n; } }; int main() { std::cout \n Example 4: Custom Deleters \n; // 用例1使用 unique_ptr 管理 C 风格文件句柄指定函数指针作为删除器 // 删除器类型是 void(*)(std::FILE*)成为 unique_ptr 类型的一部分 std::unique_ptrstd::FILE, decltype(FileDeleter) filePtr(fopen(test.txt, w), FileDeleter); if (filePtr) { std::fputs(Hello, custom deleter!\n, filePtr.get()); // 文件会在 filePtr 离开作用域时自动关闭 } // 用例2使用 unique_ptr 管理动态数组使用函数对象作为删除器 std::unique_ptrint, ArrayDeleter arrayPtr(new int[10]{1,2,3}, ArrayDeleter{}); // 可以像数组一样使用但注意 unique_ptrT[] 有特化版本更推荐。 for(int i 0; i 3; i) { std::cout arrayPtr.get()[i] ; } std::cout std::endl; // 用例3使用 shared_ptr 管理数组删除器类型不是 shared_ptr 类型的一部分 // shared_ptrint 默认删除器是 delete不适用于数组。需要自定义。 std::shared_ptrint sharedArray(new int[5]{10,20,30}, [](int* p) { delete[] p; std::cout Array deleted via lambda for shared_ptr.\n; }); // 注意shared_ptrT[] 在 C17 才有特化。C11/14 中这样用是未定义行为UB // 因为 shared_ptrint 的默认删除器是 delete不是 delete[]。 // 因此对于数组必须提供正确的删除器或者使用 std::vector。 std::cout Leaving main scope, resources will be auto-released...\n; return 0; }运行结果 Example 4: Custom Deleters Hello, custom deleter! File closed via function deleter. 1 2 3 int[] array deleted via functor. Leaving main scope, resources will be auto-released... Array deleted via lambda for shared_ptr.关键解析与避坑指南unique_ptr的删除器是类型的一部分这意味着两个拥有不同删除器类型的unique_ptr是不同类型不能互相赋值或放在同一个容器里除非使用类型擦除如std::function。这增加了类型安全但降低了灵活性。decltype在这里用于自动推导删除器类型让代码更简洁。shared_ptr的删除器不是类型的一部分shared_ptrT的删除器保存在控制块中通过类型擦除技术实现。这意味着不同删除器的shared_ptrT仍然是相同类型可以互相赋值、放入同一容器。这提供了更大的灵活性。管理数组的注意事项C14/17 之前对于unique_ptr推荐使用std::unique_ptrT[]这个特化版本它默认使用delete[]并且提供了operator[]。对于shared_ptr必须显式提供delete[]删除器或者更佳实践是直接使用std::vector或std::array。C17 及以后可以使用std::shared_ptrT[]特化版本。Lambda 表达式作为删除器非常方便尤其是在shared_ptr中可以就地定义清理逻辑代码紧凑清晰。3.5 示例五智能指针在实战中的综合应用工厂模式与缓存让我们看一个更接近真实项目的例子一个简单的键值对缓存。它使用工厂函数创建对象并用shared_ptr在缓存中共享它们同时使用weak_ptr来返回不延长生命周期的引用以便在缓存决定淘汰对象时能正常释放。#include iostream #include memory #include unordered_map #include string class ExpensiveObject { public: explicit ExpensiveObject(const std::string key) : key_(key) { std::cout [ key_ ] Constructing (expensive operation)...\n; // 模拟昂贵的初始化如加载文件、建立网络连接等 data_ Data for key_; } ~ExpensiveObject() { std::cout [ key_ ] Destroying...\n; } const std::string getData() const { return data_; } private: std::string key_; std::string data_; }; class ObjectCache { public: // 工厂函数获取或创建对象 std::shared_ptrExpensiveObject get(const std::string key) { std::cout \nCache lookup for key: \ key \\n; // 1. 查找缓存 auto it cache_.find(key); if (it ! cache_.end()) { // 2. 找到 weak_ptr尝试提升为 shared_ptr if (auto sp it-second.lock()) { std::cout - Cache HIT. Reusing existing object.\n; return sp; // 返回 shared_ptr引用计数增加 } else { // 3. weak_ptr 已过期对象已被释放从缓存中移除无效条目 std::cout - Stale entry found, removing...\n; cache_.erase(it); } } // 4. 缓存未命中创建新对象 std::cout - Cache MISS. Creating new object.\n; auto sp std::make_sharedExpensiveObject(key); // 5. 将 shared_ptr 的 weak_ptr 形式存入缓存 cache_[key] sp; // 隐式转换 shared_ptr - weak_ptr return sp; } // 清理过期条目可选 void cleanup() { size_t before cache_.size(); for (auto it cache_.begin(); it ! cache_.end(); ) { if (it-second.expired()) { // expired() 检查 weak_ptr 是否指向已释放对象 it cache_.erase(it); } else { it; } } std::cout Cache cleanup removed (before - cache_.size()) stale entries.\n; } size_t size() const { return cache_.size(); } private: // 缓存存储 weak_ptr不会阻止对象被销毁 std::unordered_mapstd::string, std::weak_ptrExpensiveObject cache_; }; int main() { std::cout Example 5: Cache with shared_ptr and weak_ptr \n; ObjectCache cache; { std::cout \n--- First access to \config\ ---\n; auto obj1 cache.get(config); std::cout Data: obj1-getData() std::endl; std::cout Cache size: cache.size() std::endl; // 1个条目weak_ptr std::cout \n--- Second access to \config\ (within same scope) ---\n; auto obj2 cache.get(config); // 应该命中缓存 std::cout Data: obj2-getData() std::endl; std::cout Are they the same object? std::boolalpha (obj1.get() obj2.get()) std::endl; } // obj1 和 obj2 离开作用域强引用计数归零对象被销毁 std::cout \n--- Access after object destruction ---\n; cache.cleanup(); // 清理过期的 weak_ptr 条目 std::cout Cache size after cleanup: cache.size() std::endl; // 应为 0 { std::cout \n--- Access again, should re-create ---\n; auto obj3 cache.get(config); // 缓存条目已清理需要重新创建 std::cout Data: obj3-getData() std::endl; } return 0; }运行结果分析 Example 5: Cache with shared_ptr and weak_ptr --- First access to config --- Cache lookup for key: config - Cache MISS. Creating new object. [config] Constructing (expensive operation)... Data: Data for config Cache size: 1 --- Second access to config (within same scope) --- Cache lookup for key: config - Cache HIT. Reusing existing object. Data: Data for config Are they the same object? true [config] Destroying... --- Access after object destruction --- Cache cleanup removed 1 stale entries. Cache size after cleanup: 0 --- Access again, should re-create --- Cache lookup for key: config - Cache MISS. Creating new object. [config] Constructing (expensive operation)... Data: Data for config [config] Destroying...这个示例完美展示了shared_ptr和weak_ptr的协作缓存命中当对象还存在时weak_ptr::lock()成功返回shared_ptr共享对象避免了重复创建的开销。自动清理当所有外部的shared_ptr都销毁后obj1,obj2出作用域对象被释放。缓存中对应的weak_ptr自动“过期”expired()返回true。资源管理缓存本身ObjectCache只持有weak_ptr因此它不会阻止对象的生命周期。对象的生死由外部使用者的shared_ptr决定符合“谁使用谁负责”的直观逻辑。实战经验这种“持有weak_ptr的缓存”模式非常常见且有效比如在图形渲染中缓存纹理、在游戏引擎中缓存资源、在网络服务中缓存连接等。weak_ptr::expired()和lock()的组合是检查并安全获取对象的标准做法。注意if (!wp.expired()) { auto sp wp.lock(); ... }这种写法在多线程环境下不是原子的可能存在竞态条件在expired()和lock()之间对象被销毁。安全的做法是直接if (auto sp wp.lock()) { ... }因为lock()本身是原子的。4. 常见陷阱、性能考量与最佳实践即使理解了原理在实际项目中稍有不慎还是会掉进坑里。这里我总结了一些最常见的陷阱和对应的最佳实践。4.1 陷阱一误用get()获取的裸指针问题sp.get()返回的裸指针生命周期是“借用”来的它的有效性完全依赖于智能指针本身。如果你用这个裸指针去创建另一个智能指针或者手动删除它灾难就发生了。auto sp std::make_sharedint(42); int* rawPtr sp.get(); { std::shared_ptrint sp2(rawPtr); // 错误sp2 会创建新的控制块导致双重释放。 } // sp2 析构释放内存 // ... 后续 sp 析构时会再次释放同一块内存 - 程序崩溃。解决方案绝对不要用get()返回的指针去初始化或重置另一个智能指针。get()返回的指针只应用于需要传递裸指针的 API 调用比如一些 C 接口并且要确保在该 API 调用期间原始的智能指针对象必须一直存在。4.2 陷阱二shared_ptr的循环引用这是老生常谈但极易犯错的问题示例三已经详细展示。关键在于设计时就要思考对象间的所有权关系。如果必须是双向关联那么至少有一方应该使用weak_ptr。4.3 陷阱三性能开销与make_shared的优势开销来源控制块内存shared_ptr需要额外分配控制块存储引用计数、弱引用计数、删除器等。原子操作引用计数的增减需要是线程安全的这通常意味着原子操作比普通整数操作慢。内存局部性对象和控制块分开分配可能降低缓存命中率。make_shared的优化std::make_sharedT(args...)在大多数实现中会一次性分配一块足够大的内存同时容纳T对象和控制块。这带来了两个好处提高性能减少一次内存分配提高内存局部性。避免异常安全问题考虑表达式foo(std::shared_ptrT(new T), bar())。编译器可能先new T然后调用bar()最后构造shared_ptr。如果bar()抛出异常那么new T分配的内存就泄漏了。使用make_shared将new和shared_ptr构造合为一步是异常安全的。因此最佳实践是优先使用make_shared和make_uniqueC14来创建智能指针。4.4 陷阱四与this指针的纠缠在类的成员函数中如果需要将当前对象this包装成shared_ptr传递出去非常危险。因为你可能无意中创建了多个控制块。class BadClass { public: std::shared_ptrBadClass getShared() { return std::shared_ptrBadClass(this); // 错误如果多个调用者都这样做会创建多个控制块。 } };解决方案让类继承自std::enable_shared_from_thisT。class GoodClass : public std::enable_shared_from_thisGoodClass { public: std::shared_ptrGoodClass getShared() { return shared_from_this(); // 安全返回一个与现有控制块共享的 shared_ptr } }; // 注意必须在对象已经被某个 shared_ptr 管理之后才能调用 shared_from_this()。 auto obj std::make_sharedGoodClass(); auto sp obj-getShared(); // 正确4.5 最佳实践总结默认使用unique_ptr明确独占所有权零额外开销与裸指针相当。需要共享时再用shared_ptr仔细评估所有权是否真的需要共享。使用make_shared和make_unique更安全、更高效。使用weak_ptr打破循环引用或做观察者。避免使用裸指针进行所有权管理将new和delete的出现限制在极小的、可控的范围内比如在自定义删除器内部或者低级别资源封装类的实现中。注意线程安全shared_ptr的引用计数操作是原子的因此多个线程同时拷贝/析构指向同一对象的shared_ptr是安全的。但通过shared_ptr访问对象本身并不是线程安全的需要额外的同步机制如互斥锁。不要滥用智能指针对于局部变量、成员变量等生命周期明确的对象直接使用栈对象或普通成员即可。智能指针是为了管理动态分配的生命周期不确定的资源。从裸指针到智能指针不仅仅是学习几个新类更是拥抱一种更安全、更现代的C资源管理哲学。它通过编译器的力量将程序员从繁琐且易错的手动内存管理中解放出来。我强烈建议你在所有新项目中都将智能指针作为动态内存管理的默认选项。开始时可能会觉得语法有点绕但一旦习惯你就会再也回不去那个整天提心吊胆检查delete的时代了。