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

究竟什么时候用shared_ptr,什么时候用unique_ptr?

博主介绍:程序喵大人

  • 35 - 资深C/C++/Rust/Android/iOS客户端开发
  • 10年大厂工作经验
  • 嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手
  • 《C++20高级编程》《C++23高级编程》等多本书籍著译者
  • 更多原创精品文章,首发gzh,见文末
  • 👇👇记得订阅专栏,以防走丢👇👇
    😉C++基础系列专栏
    😃C语言基础系列专栏
    🤣C++大佬养成攻略专栏
    🤓C++训练营
    👉🏻个人网站

最近,有同学来问我,想了解C++的三种智能指针的使用场景,在项目中应该如何选择?

首先要了解这三种智能指针的特点,std::unique_ptrstd::shared_ptrstd::weak_ptr

std::unique_ptr

std::unique_ptr是一种独占所有权的智能指针,意味着同一时间内只能有一个unique_ptr指向一个特定的对象。

unique_ptr被销毁时,它所指向的对象也会被销毁。

使用场景:

  • 当你需要确保一个对象只被一个指针所拥有时。
  • 当你需要自动管理资源,如文件句柄或互斥锁时。
  • 当你不确定用哪种智能指针时,优先选择unique_ptr就没毛病。

示例代码:

#include <iostream>
#include <memory>class Test {
public:Test() { std::cout << "Test::Test()\n"; }~Test() { std::cout << "Test::~Test()\n"; }void test() { std::cout << "Test::test()\n"; }
};int main() {std::unique_ptr<Test> ptr(new Test());ptr->test();// 当ptr离开作用域时,它指向的对象会被自动销毁return 0;
}

std::shared_ptr

std::shared_ptr是一种共享所有权的智能指针,多个shared_ptr可以指向同一个对象。内部使用引用计数来确保只有当最后一个指向对象的shared_ptr被销毁时,对象才会被销毁。

使用场景:

  • 当你需要在多个所有者之间共享对象时。
  • 当你需要通过复制构造函数或赋值操作符来复制智能指针时。

示例代码:

#include <iostream>
#include <memory>class Test {
public:Test() { std::cout << "Test::Test()\n"; }~Test() { std::cout << "Test::~Test()\n"; }void test() { std::cout << "Test::test()\n"; }
};int main() {std::shared_ptr<Test> ptr1(new Test());std::shared_ptr<Test> ptr2 = ptr1;ptr1->test();// 当ptr1和ptr2离开作用域时,它们指向的对象会被自动销毁return 0;
}

std::weak_ptr

std::weak_ptr是一种不拥有对象所有权的智能指针,它指向一个由std::shared_ptr管理的对象。weak_ptr用于解决shared_ptr之间的循环引用问题。

是另外一种智能指针,它是对 shared_ptr 的补充,std::weak_ptr 是一种弱引用智能指针,用于观察 std::shared_ptr 指向的对象,而不影响引用计数。它主要用于解决循环引用问题,从而避免内存泄漏,另外如果需要追踪指向某个对象的第一个指针,则可以使用 weak_ptr。

可以考虑在对象本身中维护一个指向第一个 shared_ptr 的弱引用(std::weak_ptr)。当创建对象的第一个 shared_ptr 时,将这个 shared_ptr 赋值给对象的 weak_ptr 成员。这样,在需要时,可以通过检查对象的 weak_ptr 成员来获取指向对象的第一个 shared_ptr(如果仍然存在的话).

使用场景:

  • 当你需要访问但不拥有由shared_ptr管理的对象时。
  • 当你需要解决shared_ptr之间的循环引用问题时。
  • 注意weak_ptr肯定要和shared_ptr搭配使用。

示例代码:

#include <iostream>
#include <memory>class Test {
public:Test() { std::cout << "Test::Test()\n"; }~Test() { std::cout << "Test::~Test()\n"; }void test() { std::cout << "Test::test()\n"; }
};int main() {std::shared_ptr<Test> sharedPtr(new Test());std::weak_ptr<Test> weakPtr = sharedPtr;if (auto lockedSharedPtr = weakPtr.lock()) {lockedSharedPtr->test();}// 当sharedPtr离开作用域时,它指向的对象会被自动销毁return 0;
}

这三种智能指针各有其用途,选择哪一种取决于你的具体需求。

1)智能指针方面的建议:

  • 尽量使用智能指针,而非裸指针来管理内存,很多时候利用RAII机制管理内存肯定更靠谱安全的多。
  • 如果没有多个所有者共享对象的需求,建议优先使用unique_ptr管理内存,它相对shared_ptr会更轻量一些。
  • 在使用shared_ptr时,一定要注意是否有循环引用的问题,因为这会导致内存泄漏。
  • shared_ptr的引用计数是安全的,但是里面的对象不是线程安全的,这点要区别开。

2)为什么std::unique_ptr可以做到不可复制,只可移动?

因为把拷贝构造函数和赋值运算符标记为了delete,见源码:

template <typename _Tp, typename _Tp_Deleter = default_delete<_Tp> > 
class unique_ptr {// Disable copy from lvalue.unique_ptr(const unique_ptr&) = delete;template<typename _Up, typename _Up_Deleter> unique_ptr(const unique_ptr<_Up, _Up_Deleter>&) = delete;unique_ptr& operator=(const unique_ptr&) = delete;template<typename _Up, typename _Up_Deleter> unique_ptr& operator=(const unique_ptr<_Up, _Up_Deleter>&) = delete;
};

3)shared_ptr的原理:

每个 std::shared_ptr 对象包含两个成员变量:一个指向被管理对象的原始指针,一个指向引用计数块的指针(control block pointer)。

引用计数块是一个单独的内存块,引用计数块允许多个 std::shared_ptr 对象共享相同的引用计数,从而实现共享所有权。

当创建一个新的 std::shared_ptr 时,引用计数初始化为 1,表示对象当前被一个 shared_ptr 管理。

  1. 拷贝 std::shared_ptr:当用一个 shared_ptr 拷贝出另一个 shared_ptr 时,需要拷贝两个成员变量(被管理对象的原始指针和引用计数块的指针),并同时将引用计数值加 1。这样,多个 shared_ptr 对象可以共享相同的引用计数。
  2. 析构 std::shared_ptr:当 shared_ptr 对象析构时,引用计数值减 1。然后检测引用计数是否为 0。如果引用计数为 0,说明没有其他 shared_ptr 对象指向该资源,因此需要同时删除原始对象(通过调用自定义删除器,如果有的话)。

4)智能指针的缺点

  1. 性能开销,需要额外的内存来存储他们的控制块,控制块包括引用计数,以及运行时的原子操作来增加或减少引用技术,这可能导致裸指针的性能下降。
  2. 循环引用问题,如果两个对象通过成员变量shared_ptr相互引用,并且没有其他指针指向这两个对象中的任何一个,那么这两个对象的内存将永远不会被释放,导致内存泄露。
#include<iostream>
#include<memory>
class B;// 前向声明class A {
public:std::shared_ptr<B> b_ptr;~A() {std::cout << "A has been destroyed."<< std::endl;}
};class B {
public:std::shared_ptr<A> a_ptr;~B() {std::cout << "B has been destroyed."<< std::endl;}
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->b_ptr = b; // A 引用 Bb->a_ptr = a; // B 引用 A// 由于存在循环引用,A 和 B 的析构函数将不会被调用,从而导致内存泄漏return 0;
}
  1. 智能指针不一定适用于所有场景:有一些容器类,内部实现依赖于裸指针,另外在考虑某些性能关键场景下,使用裸指针可能更合适。但绝大多数场景,用智能指针就OK。

选型建议

  1. 默认选择unique_ptr,因为它性能最优,且语义清晰,比如局部动态对象。

  2. 当你发现unique_ptr使用受限,那大概率就是有需要共享的需求,需要多个模块或对象需共享同一资源时(如全局配置、线程间共享数据),使用shared_ptr,但要注意循环引用的问题。

  3. 优先使用make_uniquemake_shared构造对应的智能指针,具备异常安全性。

  4. 避免裸指针和智能指针混用,容易出现double free等问题。

  5. unique_ptr放心使用,并没有额外开销。

  6. shared_ptr 的引用计数可能引发原子操作开销,除非对性能有非常极致的要求,否则没必要在意这点开销。也要注意循环引用会导致内存泄漏。

码字不易,欢迎大家点赞,关注,评论,谢谢!

👉C++训练营

一个专为校招、社招3年工作经验的同学打造的 1v1 项目实战训练营,量身定制学习计划、每日代码review,简历优化,面试辅导,已帮助多名学员获得大厂offer!


文章转载自:

http://BaRPM7xg.sLfkt.cn
http://RmuTGI4L.sLfkt.cn
http://iTtFfEYn.sLfkt.cn
http://yDewk042.sLfkt.cn
http://V5BEkZiM.sLfkt.cn
http://QIkC1tyr.sLfkt.cn
http://cnQefK4Z.sLfkt.cn
http://V7aMzX2m.sLfkt.cn
http://wQuHCQ2G.sLfkt.cn
http://tWDvQH9F.sLfkt.cn
http://yEre5AKF.sLfkt.cn
http://W59cFsOp.sLfkt.cn
http://kLvFndxs.sLfkt.cn
http://KkzjLHPG.sLfkt.cn
http://t6EnhYJ3.sLfkt.cn
http://Y4OR0B0c.sLfkt.cn
http://vQLMQm4v.sLfkt.cn
http://fQ2ug5is.sLfkt.cn
http://MyVwHTqw.sLfkt.cn
http://ltF9nT40.sLfkt.cn
http://SxrDxRZL.sLfkt.cn
http://B4RZdo92.sLfkt.cn
http://z8mxOgAU.sLfkt.cn
http://W8m9uNZo.sLfkt.cn
http://OwNGBT4e.sLfkt.cn
http://sBROZ2Nl.sLfkt.cn
http://5zSwnqCC.sLfkt.cn
http://gGkKduIW.sLfkt.cn
http://zhnZJgZB.sLfkt.cn
http://im2QH7EK.sLfkt.cn
http://www.dtcms.com/a/378583.html

相关文章:

  • 前端抽象化,打破框架枷锁:react现代化项目中的思想体现
  • 基于开源AI智能名片、链动2+1模式与S2B2C商城小程序的流量运营与个人IP构建研究
  • gstreamer:创建组件、管道和总线,实现简单的播放器(Makefile,代码测试通过)
  • Kibana 双栈网络(Dual-Stack)支持能力评估
  • go 日志的分装和使用 Zap + lumberjack
  • 河北智算中心绿色能源占比多少?
  • 在能源互联网时代天硕工业级SSD固态硬盘为何更受青睐?
  • 关于rust的crates.io
  • 使用Rust实现服务配置/注册中心
  • C++ 类与对象(下):从构造函数到编译器优化深度解析
  • DNS 域名解析
  • EasyDSS重装系统后启动失败?解决RTMP推流平台EasyDss服务启动失败的详细步骤
  • 自动驾驶中的传感器技术45——Radar(6)
  • 第四章 Elasticsearch索引管理与查询优化
  • 拆分了解HashMap的数据结构
  • Sqlite“无法加载 DLL“e_sqlite3”: 找不到指定的模块”解决方法
  • 项目 PPT 卡壳?模型效果 + 训练数据展示模块直接填 ,451ppt.vip预制PPT也香
  • react-native项目通过华为OBS预签名url实现前端直传
  • Linux-> UDP 编程1
  • Pytest+requests进行接口自动化测试2.0(yaml)
  • 【容器使用】如何使用 docker 和 tar 命令来操作容器镜像
  • 科普:在Windows个人电脑上使用Docker的极简指南
  • 【面试场景题】电商订单系统分库分表方案设计
  • 微服务保护全攻略:从雪崩到 Sentinel 实战
  • springcloud二-Sentinel
  • Redis 持久化与高可用实践(RDB / AOF / Sentinel / Cluster 全解析)
  • Semaphore 信号量深度解析
  • 门店网络重构:告别“打补丁”,用“云网融合”重塑数字竞争力!
  • Linux操作系统之Ubuntu
  • WSL自定义安装多个相同版本的Ubuntu子系统