当前位置: 首页 > news >正文

C++智能指针深度比较:“std::shared_ptr“ vs “std::unique_ptr“ vs “std::weak_ptr“

1. 核心概念对比

特性std::unique_ptrstd::shared_ptrstd::weak_ptr
所有权独占所有权共享所有权无所有权(弱引用)
拷贝语义不可拷贝,只能移动可拷贝,引用计数增加可拷贝,不增加引用计数
资源释放时机所有者销毁时最后一个shared_ptr销毁时不负责释放资源
性能开销几乎零开销(与裸指针相当)引用计数开销(原子操作)引用计数开销
内存布局单个指针大小两个指针大小(对象指针+控制块指针)单个指针大小
线程安全非线程安全(需外部同步)引用计数操作线程安全,对象访问需同步引用计数操作线程安全

2. 详细特性分析

2.1std::unique_ptr

设计哲学:独占所有权,零开销抽象

// 独占所有权,不能共享std::unique_ptr<int>ptr1=std::make_unique<int>(42);// std::unique_ptr<int> ptr2 = ptr1; // 错误!不能拷贝std::unique_ptr<int>ptr2=std::move(ptr1);// 只能移动转移所有权// 自定义删除器(编译时确定)autodeleter=[](int*p){std::cout<<"Deleting int\n";deletep;};std::unique_ptr<int,decltype(deleter)>ptr3(newint(10),deleter);// 数组版本std::unique_ptr<int[]>arr=std::make_unique<int[]>(10);arr[0]=1;// 支持operator[]

优点

  • 零开销(编译器优化后与裸指针相同)
  • 明确的独占所有权语义
  • 编译时多态(通过自定义删除器模板参数)
  • 适合资源管理、RAII模式

缺点

  • 不能共享所有权
  • 不能用于需要共享的场景

2.2std::shared_ptr

设计哲学:共享所有权,引用计数

#include<memory>#include<iostream>classResource{public:Resource(){std::cout<<"Resource created\n";}~Resource(){std::cout<<"Resource destroyed\n";}};voidusage(){// 创建shared_ptrautosp1=std::make_shared<Resource>();// 引用计数: 1{autosp2=sp1;// 拷贝,引用计数: 2autosp3=sp1;// 拷贝,引用计数: 3std::cout<<"sp1.use_count() = "<<sp1.use_count()<<"\n";// 3}// sp2, sp3销毁,引用计数: 1// 控制块包含:引用计数、弱引用计数、删除器、分配器等std::cout<<"sp1.use_count() = "<<sp1.use_count()<<"\n";// 1}// sp1销毁,引用计数: 0,资源被释放// 循环引用问题示例structNode{std::shared_ptr<Node>next;~Node(){std::cout<<"Node destroyed\n";}};voidcircularReference(){autonode1=std::make_shared<Node>();autonode2=std::make_shared<Node>();node1->next=node2;// 引用计数: node1=1, node2=2node2->next=node1;// 引用计数: node1=2, node2=2 - 循环引用!// 函数结束时,引用计数都变为1,内存泄漏!}// 自定义删除器(运行时确定)voidcustomDeleter(int*p){std::cout<<"Custom delete\n";deletep;}autosp=std::shared_ptr<int>(newint(42),customDeleter);

优点

  • 允许共享所有权
  • 自动管理生命周期
  • 线程安全的引用计数
  • 支持自定义删除器(运行时多态)

缺点

  • 引用计数开销(特别是原子操作)
  • 控制块额外内存开销
  • 可能导致循环引用

2.3std::weak_ptr

设计哲学:弱引用,避免所有权

classObserver;classSubject{std::vector<std::weak_ptr<Observer>>observers;public:voidaddObserver(std::weak_ptr<Observer>obs){observers.push_back(obs);}voidnotify(){for(auto&weakObs:observers){if(autoobs=weakObs.lock()){// 尝试获取shared_ptr// 如果对象还存在,通知它obs->onNotify();}else{// 对象已被释放,可以清理弱引用}}}};classObserver{public:voidonNotify(){std::cout<<"Notified!\n";}};// 使用示例voidobserverPattern(){autosubject=std::make_shared<Subject>();autoobserver=std::make_shared<Observer>();subject->addObserver(observer);// 传递weak_ptr// observer可以在其他地方被释放,不会影响subjectobserver.reset();// 释放observersubject->notify();// 安全,不会访问已释放的对象}// 解决循环引用structSafeNode{std::weak_ptr<SafeNode>next;// 使用weak_ptr避免循环引用~SafeNode(){std::cout<<"SafeNode destroyed\n";}};voidnoCircularReference(){autonode1=std::make_shared<SafeNode>();autonode2=std::make_shared<SafeNode>();node1->next=node2;// weak_ptr不增加引用计数node2->next=node1;// 引用计数保持为1// 函数结束时,两个节点都能正确释放}

优点

  • 打破循环引用
  • 实现观察者模式
  • 缓存实现(不影响对象生命周期)
  • 安全的对象访问(通过lock()检查)

缺点

  • 需要额外步骤获取对象(lock()
  • 不能直接访问对象

3. 性能比较

#include<memory>#include<chrono>#include<iostream>constintITERATIONS=10000000;voidtestUniquePtr(){autostart=std::chrono::high_resolution_clock::now();for(inti=0;i<ITERATIONS;++i){autoptr=std::make_unique<int>(i);// 移动操作autoptr2=std::move(ptr);}autoend=std::chrono::high_resolution_clock::now();std::chrono::duration<double>diff=end-start;std::cout<<"unique_ptr: "<<diff.count()<<"s\n";}voidtestSharedPtr(){autostart=std::chrono::high_resolution_clock::now();for(inti=0;i<ITERATIONS;++i){autoptr=std::make_shared<int>(i);autoptr2=ptr;// 拷贝,引用计数操作autoptr3=ptr;// 再次拷贝}autoend=std::chrono::high_resolution_clock::now();std::chrono::duration<double>diff=end-start;std::cout<<"shared_ptr: "<<diff.count()<<"s\n";}// 典型结果(10,000,000次操作):// unique_ptr: 0.15s// shared_ptr: 1.20s (约8倍慢)

内存布局对比

structObject{intdata[100];};// unique_ptr: 一个指针(8字节)std::unique_ptr<Object>up=std::make_unique<Object>();// shared_ptr: 两个指针(16字节)+ 控制块(~32字节)std::shared_ptr<Object>sp=std::make_shared<Object>();// make_shared将对象和控制块分配在一起,提高局部性// shared_ptr<Object> sp(new Object()); // 分离分配,较差

4. 使用场景总结

使用std::unique_ptr的场景:

// 1. 工厂函数返回std::unique_ptr<Database>createDatabase(){returnstd::make_unique<MySQLDatabase>();}// 2. 独占资源管理classConnection{std::unique_ptr<Socket>socket;// 独占socketstd::unique_ptr<Buffer>buffer;// 独占buffer};// 3. Pimpl惯用法classMyClass{structImpl;std::unique_ptr<Impl>pimpl;};// 4. 容器中的对象std::vector<std::unique_ptr<Shape>>shapes;shapes.push_back(std::make_unique<Circle>());

使用std::shared_ptr的场景:

// 1. 共享缓存classCache{staticstd::unordered_map<std::string,std::shared_ptr<Resource>>cache;staticstd::shared_ptr<Resource>get(conststd::string&key){returncache[key];// 共享所有权}};// 2. 共享配置classAppConfig{std::shared_ptr<Config>config;// 多个组件共享同一配置};// 3. 需要延长对象生命周期的回调classAsyncOperation{std::shared_ptr<Callback>callback;// 确保回调对象在操作完成前存活};// 4. 多线程共享数据classThreadPool{std::shared_ptr<TaskQueue>queue;// 多个工作线程共享任务队列};

使用std::weak_ptr的场景:

// 1. 打破循环引用classParent{std::shared_ptr<Child>child;};classChild{std::weak_ptr<Parent>parent;// 使用weak_ptr};// 2. 观察者模式classSubject{std::vector<std::weak_ptr<Observer>>observers;};// 3. 缓存实现classCache{std::unordered_map<std::string,std::weak_ptr<Resource>>weakCache;std::shared_ptr<Resource>get(conststd::string&key){if(autoit=weakCache.find(key);it!=weakCache.end()){if(autoresource=it->second.lock()){returnresource;// 对象还存在}weakCache.erase(it);// 对象已释放,清理}// 重新加载...}};// 4. 临时对象引用classProcessor{std::weak_ptr<TempData>tempData;// 临时数据,可能被提前释放};

5. 最佳实践总结

  1. 优先使用std::unique_ptr

    • 默认选择,除非需要共享所有权
    • 性能最佳,语义最清晰
  2. 谨慎使用std::shared_ptr

    • 只在真正需要共享所有权时使用
    • 注意循环引用问题
    • 优先使用std::make_shared
  3. 合理使用std::weak_ptr

    • 解决循环引用
    • 实现观察者、缓存等模式
    • 总是通过lock()检查有效性
  4. 选择原则

    • 需要独占所有权 →unique_ptr
    • 需要共享所有权 →shared_ptr
    • 需要弱引用/打破循环 →weak_ptr
    • 原始指针/引用 → 用于无所有权场景
  5. 性能考虑

    • 热点路径避免shared_ptr
    • 小对象考虑使用unique_ptr或直接存储
    • 避免频繁创建/销毁shared_ptr
// 混合使用示例classSystem{private:std::unique_ptr<Database>db;// 独占数据库连接std::shared_ptr<Config>config;// 共享配置std::weak_ptr<Monitor>monitor;// 弱引用监控器(可能不存在)std::vector<std::unique_ptr<Task>>tasks;// 独占任务对象std::shared_ptr<Logger>logger;// 共享日志器};
http://www.jsqmd.com/news/253112/

相关文章:

  • 6.2 专业技能速成班:让AI成为你的行业专家导师
  • 6.3 结构化学习法:用AI制定完美的个人成长路径
  • 全网最全8个AI论文写作软件,专科生轻松搞定论文格式规范!
  • 6.4 编程学习伙伴:零基础也能掌握代码技能
  • 6.5 职业规划顾问:AI指导你的职业发展道路
  • 6.6 健康管理教练:打造专属的AI健康助手
  • AiScholar艾思科蓝2025年度答卷:聚焦“四化”战略,驱动创新发展
  • 企业搞定数字化的三个核心认知
  • 企业实施ERP系统的目标原则、核心步骤及成功因素
  • 全网最全自考必备AI论文软件TOP9:9款深度测评与推荐
  • 免费AI论文生成器实操指南:6款工具助你知网查重一把过不留痕
  • 高级软件测试工程师必备知识与技能
  • 国产ai数据库工具-chat2db邀请码A66666
  • 【Python开发】Pillow渲染泰文/阿拉伯文乱码?揭秘跨境电商多语言排版的底层技术
  • 【Python自动化】大促前夜的救命稻草:如何用AI实现千款商品图的“节日化”批量翻新?
  • 6.1 私人AI外教:如何打造专属你的语言学习助手?
  • 【Python自动化】一人抵十人:超级个体如何利用AI构建“无人值守”的跨境修图工厂?
  • 管家婆 + cpolar 让进销存管理随时随地搞定
  • 中国九章4.0实测:1分钟完成千亿参数模型训练
  • 救命神器10个AI论文平台,专科生轻松搞定毕业论文!
  • 使用geneHapR做单倍型分析
  • python绘制基因表达量热图
  • ‌欧盟AI法案首张罚单事件:软件测试从业者的警示与行动指南
  • 中国大模型暗战:阿里通义2.0的伦理后门测试报告
  • 深度伪造技术风暴:测试工程师的数字打假战场
  • 算法奴隶制:非洲数据标注工厂的血汗真相调查
  • 查看ai有没有学会知识的方法,打印神经网络最后一层
  • ‌人权组织指控‌:87%国家用AI监控实施种族歧视
  • 意识觉醒第一案:AI艺术家起诉人类剥夺著作权
  • 《危险边缘》:量子噪声导致AI医疗诊断集体失真事件