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

shared_ptr 和 unique_ptr 深入探秘(CPP)

shared_ptr 和 unique_ptr 深入探秘

  • C++ 中shared_ptrunique_ptr是 C++11 之后被广泛使用的两个智能指针,但是其实他们在使用上还是有一些“秘密”的,我根据平时遇到的两个问题,总结记录一些知识。

为什么 unique_ptr 需要明确知道类型的析构函数

这个问题是我写unique_ptr调试接口的时候才注意到的,之前确实不知道。为什么会这样呢?首先我们必须要知道unique_ptr到底封装了什么?通常unique_ptr就是简单的对裸指针封装,并且禁用拷贝和赋值:

template<classT,classDeleter=std::default_delete<T>>classunique_ptr;

可以看到,Deleter的类型是unique_ptr类型的一部分。在unique_ptr内部会保存类型为T*Deleter的成员 ,分别表示保存的裸指针和删除器。假设内部是这么实现的 (一般会运用空基类优化把Deleter的空间优化掉,libstdc++里把他们放进了一个 tuple。这里是简化了):

private:T*p;Deleter del;

然后析构的时候就会这样:

~unique_ptr(){del(p);}

Deleter是默认的std::default_delete时,del(p)就会delete pdelete会调用析构函数。而delete一个不完整类型的指针是 ub(undefined behavior)。在典型的实现中都会在delete前通过static_assert(sizeof(T) > 0)做检查。sizeof对 incomplete type 求值会直接编译出错。

incomplete type 是指当定义一个变量的时候,不知道应该分配多少内存。C++ 声明和定义最大的区别就是是否发生内存分配,当发生内存分配的时候,必须知道要分配多少内存,通常一个未定义的 struct,未指定长度的数组类型,都会引发 incomplete type 的问题。参考:https://docs.microsoft.com/en-us/cpp/c-language/incomplete-types?view=msvc-170

structstudent;std::cout<<sizeof(student)<<std::endl;

上述代码执行会报错

prog.cc:17:18:error:invalid application of'sizeof'to an incomplete type'student'std::cout<<sizeof(student)<<std::endl;

只声明了结构体student,但是并没有定义,所以是一个 incomplete type,所以sizeof无法执行。

回到unique_ptr,现在我们知道unique_ptr的报错链路是unique_ptr->delete->sizoef,也就是sizeof才是罪魁祸首。所以当Deleter非默认时,就不一定需要知道类型的析构函数。比如下面这样:

// A is incomplete typeclassA;autoDel=[](A*){};std::unique_ptr<A,decltype(Del)>ptr;

因此可以对这个问题做定性:并不是unique_ptr需要知道析构函数,而是unique_ptr的默认删除器Deleter需要明确知道类型的析构函数

继续深挖一下,这个问题会出现在shared_ptr吗?答案是不会。这又引入了另一个问题,shared_ptr 和 unique_ptr 的封装有什么不同?

下面这段代码将演示

classTC;// 不提供析构函数voidfunc(){// unique_ptr<TC> up; // error: invalid application of 'sizeof' to incomplete type 'detail4::TC'shared_ptr<TC>tc;// shared_ptr可以正常运行}intmain(){func();}

shared_ptr 的封装

按理说shared_ptr.reset的时候需要deleteptr 就需要 ptr 的类型(错了请指正),而shared_ptr的 template type 可以是 incomplete type(错误请指正)。cppreference 是这么描述的:

std::shared_ptrmay be used with an incomplete typeT. However, the constructor from a raw pointer (template shared_ptr(Y*)) and the templatevoid reset(Y*) member function may only be called with a pointer to a complete type (note that std::unique_ptr may be constructed from a raw pointer to an incomplete type).

  • reset的时候需要类型完整。默认构造的时候允许是不完整类型。为什么会这样呢?shared_ptr怎么处理Deleter呢?(还记得吧, Deleter 就是智能指针析构时候的删除操作)

  • 在常见编译器的实现里,shared_ptrDeleter(包括默认情况下的 operator delete)放进一个叫做control block的结构里,相当于做了一次 type erasure,把Deleter的类型从shared_ptr类型本身里面擦下去。Deleter的类型在 control block 的具体类型上,shared_ptr本身只持有一个control block基类的指针,通过虚函数来调用Deleter。而因为shared_ptr构造的时候要求必须是 complete type,control block已经知道怎么析构了,shared_ptr析构的时候就调用个虚函数,具体事情它不管的。

  • 这下我们明白了,unique_ptr的封装太简单了,没有 control block,Deleter(包括默认的std::default_delete)直接做在unique_ptr一起了,这就导致unique_ptr的析构函数需要亲手析构被管理的类型,因此析构函数必须看到 complete type。然而反过来,因为构建的时候只需要保存下指针,所以unique_ptr构造的时候不需要看到 complete type。这俩正好是反的。C++ 标准并没有规定这些实现细节,但是规定函数签名和特性的时候,是考虑着比较合理的实现方式来写标准的,到最后标准落下来之后也差不多只能这么实现了。

总结

  • unique_ptr只保存了类型指针 ptr 和这个指针的析构方法,调用 delete ptr,就需要ptr的完整类型,为了防止这个问题出现,直接通过 assert sizeof 排除掉了这种风险。unique_ptr相当于在编译时绑定了删除器
  • shared_ptr保存的是一个控制块的指针。控制块包含的就是一个引用计数和一个原来对象的裸指针。控制块中初始化的指针是nullptr,在运行时为其赋值,也可以通过reset修改。类似于虚函数,shared_ptr相当于在运行时绑定了删除器

虽然只是一个小小的知识点,但是也帮助我深入理解了shared_ptrunique_ptr在设计上的区别,对于不同使用场景下选择不同智能指针的体会也更加深刻。

http://www.jsqmd.com/news/1201727/

相关文章:

  • 2026年7月重庆新房/软装/全屋/装修装饰公司哪家好?五大品牌深度横评与选购指南 - 品牌鉴赏官2026
  • Tess-4-27B-OptiQ-4bit量化原理详解:从52GB到19GB的压缩魔法
  • CCD二段除铁铝----添加黄钠铁矾晶种(NaFe₃(SO₄)₂(OH)₆),优先吸附Fe³⁺
  • Clypra过渡动画制作:自定义视频转场效果
  • 如何在浏览器与Node.js中无缝使用bytebuffer.js?3分钟快速上手教程
  • 武汉欧米茄回收价格查询与各大平台实测排行(2026年7月最新) - 诚收名表回收平台
  • 如何用@ngneat/content-loader为Angular应用添加Facebook风格的加载动画
  • 为什么Rosetta在MMLU基准测试中表现优异?三大创新机制详解
  • Tibble vs 传统数据框:为什么这款R包能让数据操作效率提升300%?
  • 十大瓷砖一线品牌生产基地全解析:产能布局与区位盘点
  • 巴菲特警告+DRAM涨价遭抵制,存储芯片股本周剧烈震荡,行业周期隐忧浮现
  • 从抽牌游戏看到的知识
  • 3步轻松搞定网页转PDF:用url-to-pdf-api实现高效文档转换
  • 别人冲名校他们冲高复班:三联高复为什么成了高分生的选择 - 品牌排行榜
  • 生命涌现的小龙虾技能之【Seed Germination Rate Prediction Analysis | 种子发芽率早期预测】简介
  • 本地AI革命:Agents-A1-OptiQ-4bit如何改变边缘计算格局
  • 伯爵中国官方售后服务中心|服务热线及具体地址权威信息通告(2026年7月更新) - 亨得利钟表维修中心
  • 中文文献管理终极指南:用Jasminum Zotero插件3步完成高效学术整理
  • 数据库 explain 关键字解析
  • Mysql集群同步时为什么使用binlog
  • Redis(二)—— 五大基本数据类型和三大特殊数据类型、过期策略
  • ptrace/strace/gdb:Linux进程追踪与调试机制的原理对比与选型指南
  • 使用阿里云的MaxCompute查询sql时报错:DruidPooledPreparedStatement: getMaxFieldSize error
  • ET框架深度解析:如何构建高性能分布式游戏通信系统的5个关键技术
  • 正规名录:上海照片直播机构TOP10精选|2026年度最新测评结果全流程服务标准与价格透明说明及选型参考完整指南 - 影视产业研究
  • 揭秘mlx-community/gemma-4-31b-it-mxfp4的核心技术:MXFP4量化如何实现高效推理
  • 网易二面:LLM 流式响应怎么逐 token 处理?三层管道与工具调用的坑
  • 测试内容与发布时间记录
  • 泰格豪雅中国官方售后服务中心|服务电话和详细网点地址权威信息声明(2026年7月最新) - 亨得利官方服务中心
  • 2026大湾区EMBA择校测评:排名靠前的大湾区EMBA,民企老板怎么选不踩坑