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

CppCon 2014 学习:Anatomy of a Smart Pointer

智能指针(smart pointer)可以这样解释:

  • 它是一个指针的容器——内部保存了一个普通指针,并且可以在需要时把指针交给你使用。
  • 它支持RAII(资源获取即初始化),也就是说资源(比如内存)会随着智能指针的生命周期自动申请和释放,避免内存泄漏。
  • 构造时有合理的默认设置,比如默认指针是空指针(nullptr)。
  • 它会在合适的时机自动清理指针所指向的资源,比如智能指针销毁时自动delete掉指针。
  • (理想情况下)它能够准确表达接口语义,比如区分“共享所有权”(shared_ptr)和“独占所有权”(unique_ptr)等不同的使用场景。
    总结来说,智能指针就是一个能自动管理内存的指针封装工具,让内存管理更安全、更方便。

智能指针其实就是指针的代理(proxy),它在很多场合可以代替普通指针使用。

  • 它“站在指针的位置”,可以像原始指针那样操作,比如访问成员、解引用等。
  • 你用智能指针时,感觉就像在用普通指针,但它帮你管理内存生命周期,避免忘记释放或重复释放。
    换句话说,智能指针封装了指针,让你不用直接操心内存管理,但用起来又跟普通指针差不多,非常方便且安全。

列出的这些都是不同种类的智能指针,它们各有特点和用途:

  • auto_ptr:早期C++标准的智能指针,有独占所有权,但存在复制时所有权转移的问题(已废弃,不推荐使用)。
  • scoped_ptr:Boost库中的智能指针,生命周期限定在作用域内,不能转移所有权(类似unique_ptr,但更简单)。
  • unique_ptr:C++11引入的智能指针,独占所有权,不能被复制,但可以移动,推荐用来管理唯一所有权的资源。
  • shared_ptr:共享所有权智能指针,多个shared_ptr可以指向同一个资源,引用计数控制资源释放。
  • weak_ptr:辅助shared_ptr的智能指针,不拥有资源,但可以观察shared_ptr管理的对象,防止循环引用。
  • _com_ptr_t:微软提供的智能指针,用于管理COM接口指针,方便COM对象的生命周期管理。
  • CComPtr:ATL库中的智能指针,也用于管理COM接口,类似_com_ptr_t,但实现不同。
    总结:
    智能指针根据用途和管理策略不同,有独占式(unique_ptr)、共享式(shared_ptr)和观察式(weak_ptr)等类型。还存在针对特定场景(比如COM对象)的专用智能指针。

“领域专用智能指针”(Domain-specific smart pointers)其实是针对特定类型或场景设计的智能指针或封装类,主要用于简化和安全管理某些特殊资源:

  • std::string:严格来说不是传统意义上的智能指针,但它内部管理动态分配的字符数组,自动负责内存分配和释放,相当于字符数组的智能管理类。
  • _bstr_t:微软提供的一个封装类,用于管理BSTR类型的字符串(Windows COM编程中用的字符串类型),自动处理内存分配和释放,避免内存泄漏。
  • _variant_t(你写的是_variant_,可能是_variant_t):也是微软COM中常用的封装类,管理VARIANT类型(能保存多种类型的数据的结构),自动管理内部资源,简化操作。
    这些“智能指针”或封装类,都是为了方便特定类型的资源管理,比如字符串或COM的特殊数据结构,让程序更安全、代码更简洁。

两种不同的引用计数(reference counting)机制,分别是:

1. 对象内部的引用计数(Object-based reference counting)

  • 引用计数变量直接存储在被管理的对象内部。
  • 对象自己维护引用计数,比如微软COM的做法就是这样。
  • 这种方式也叫做**“侵入式”(intrusive)引用计数**,因为对象必须“侵入”引用计数逻辑,自己实现AddRef/Release等方法。
  • Boost库里的boost::intrusive_ptr就是基于这种设计。
  • 优点:引用计数和对象紧密绑定,不需要额外的控制块。
  • 缺点:对象必须支持引用计数逻辑,不够灵活。

2. 容器内部的引用计数(Container-based reference counting)

  • 引用计数存储在智能指针管理的“控制块”(control block)中,和对象分开。
  • 智能指针容器(比如std::shared_ptr)管理引用计数。
  • 对象本身不需要知道引用计数的存在,设计更灵活。
  • 控制块一般存储引用计数和删除器(deleter)等信息。
  • 优点:适用于任何对象,非侵入式,更通用。
  • 缺点:需要额外的内存开销存储控制块。
    总结:
  • 侵入式引用计数:计数在对象内部,需要对象支持。
  • 非侵入式引用计数:计数在智能指针内部,适用面更广。

**对象内部引用计数(object-based reference counting)**的优缺点,具体解释如下:

优点(Advantages):

  • 智能指针逻辑非常简单:因为引用计数存在对象内部,智能指针不需要额外管理复杂的控制块。
  • 可以轻松和裸指针混用:对象自己管理引用计数,裸指针依然能共存,使用更灵活。
  • 对象对自己生命周期和清理有更大控制权:对象知道自己被引用了多少次,可以精准控制什么时候销毁。
  • 整体开销更小、更轻量:没有额外控制块,内存使用更节省。

缺点(Disadvantages):

  • COM以外的领域较不常见:这种设计主要流行于微软的COM模型,其他领域比较少见。
  • COM以外缺乏现成的实现方案:不像shared_ptr那样广泛支持和标准化。
  • 对象本身变得稍微大一点、更复杂:对象要额外包含引用计数变量和管理方法,增加设计复杂度。
    总结就是,对象内部计数适合有特定设计要求(比如COM组件),但在普通应用里不太常用,且会让对象更复杂一些。
    COM组件是指**组件对象模型(Component Object Model,简称COM)**中的“组件”——这是一种由微软提出的用于软件组件之间通信和复用的技术标准。
  • COM是一套规范和机制,用来让不同程序或不同语言写的代码(组件)能相互调用和协作。
  • 它定义了组件的二进制接口,支持跨进程、跨语言、甚至跨网络调用。
  • COM组件就是符合COM规范、可以被其他程序调用的“二进制模块”或对象,通常封装了某些功能(比如文件操作、图形处理、数据库访问等)。
  • 这些组件通过接口来暴露功能,接口都是以纯虚函数形式定义(类似C++里的抽象类)。
  • COM组件的生命周期管理很重要,使用引用计数(AddRef和Release)来控制内存和资源释放。
    举个简单比喻,COM组件就像一个“黑盒子”,别人通过它公开的接口调用它的功能,但具体实现对调用方是透明的。微软Windows系统中很多底层服务和应用都基于COM,比如ActiveX控件、DirectX、OLE等。

**容器内部引用计数(container-based reference counting)**的优缺点,具体解释如下:

优点(Advantages):

  • 可以管理任何类型的对象,对象本身不需要知道引用计数的存在,也不需要增加额外的成员变量。
  • 设计标准且广泛使用,比如C++标准库里的std::shared_ptr就是这种类型的典型代表。
  • 灵活性高,适用于各种场景和类型,不局限于特定对象设计。

缺点(Disadvantages):

  • 与裸指针混用较难,因为引用计数和指针对象是分开的,裸指针不会自动维护计数,可能引发管理混乱。
  • 实现较复杂,开销较大,为了实现线程安全、控制块管理等功能,shared_ptr等智能指针内部结构比较复杂,消耗更多资源。
    总结就是,容器式引用计数适合大多数通用场景,使用方便且标准化,但相对重量级且不适合和裸指针直接混用。

**对象内部引用计数(object-based reference counting)**的工作原理,简要总结如下:

  • 在对象内部放一个原子计数器(atomic count),用来记录当前有多少引用指向这个对象。
  • 提供增加引用和释放引用的方法,通常叫 AddRef()Release(),分别用来增加和减少计数。
  • 当引用计数减到零时,对象自己调用 delete this 来销毁自己,自动释放资源。
    这个模型比较简单直接,引用计数是对象自己维护的,智能指针或外部代码通过调用这些方法来管理生命周期。

关于 auto_ptr 的工作原理和特点:

  • auto_ptr 内部包含一个裸指针和一个所有权标志
  • 它的所有权会从一个 auto_ptr 转移到另一个 auto_ptr,也就是说当你复制或赋值 auto_ptr,原来的 auto_ptr 会失去对资源的所有权,新 auto_ptr 变成唯一拥有者。
  • 这种所有权转移的语义很容易导致错误,比如复制后原指针变为空,使用不当会导致资源提前释放或悬挂指针。
  • 因为这些缺点,auto_ptr 在C++11以后被弃用(deprecated),推荐使用更安全、语义更清晰的unique_ptr
    简单来说,auto_ptr的设计太容易出错,现代代码基本不用了。

关于 scoped_ptrunique_ptr 的工作原理:

  • 它们内部都包含一个裸指针,用来管理资源。
  • scoped_ptr 不允许转移所有权,资源的生命周期严格绑定在作用域内,超出作用域自动释放,逻辑非常简单。
  • unique_ptr 允许通过“移动语义”(move)转移所有权,这样可以把资源从一个unique_ptr转移给另一个,避免复制带来的错误。
  • unique_ptr 支持自定义删除器(custom deleter),可以指定函数或函数对象来替代默认的 delete,更灵活地管理资源释放,比如释放数组或关闭文件句柄等。
    总结:
  • scoped_ptr 适合简单的作用域内资源管理,不可转移所有权。
  • unique_ptr 更灵活,支持移动和自定义删除器,是现代C++管理唯一所有权资源的首选。

关于 shared_ptr 的工作原理,高级概述如下:

  • shared_ptr 内部包含一个指向管理对象的裸指针,还有一个共享的引用计数(reference count),这个计数被所有指向同一个对象的shared_ptr实例共享。
  • 它支持自定义删除器(custom deleter),可以在对象释放时调用特定的清理函数,而不是默认的delete
  • 每当一个新的shared_ptr拷贝或赋值时,引用计数会增加;当一个shared_ptr销毁或重置时,引用计数减少。
  • 最后一个shared_ptr引用计数减到0时,自动删除管理的对象,释放资源。
    总结就是,shared_ptr通过共享引用计数实现多个智能指针共享资源的所有权,自动管理内存,避免内存泄漏。
    在这里插入图片描述

关于 shared_ptr 的快速概览和一个简化实现的关键点,具体总结如下:

核心结构

  • 两个 shared_ptr<T> 实例:表示多个 shared_ptr 可以管理同一个对象,通过共享引用计数实现资源管理。
  • 通过类型转换生成的 shared_ptr<T2>:比如子类指针转成父类指针,或者父类转子类(安全的转换),实现多态智能指针管理。
  • reference_container(控制块):这是一个内部结构,负责保存:
    • 指向被管理对象的裸指针(T*
    • 引用计数(强引用和弱引用)
    • 自定义删除器(deleter)

关于删除器(Deleter)和分配器(Allocator)

  • 删除器只存储在控制块(reference_container)里,不是存在每个 shared_ptr 对象里。
  • 删除器和分配器是**shared_ptr 构造时的参数**,但它们不是 shared_ptr 类型本身的成员。
  • 这样设计避免了增加每个 shared_ptr 实例的大小,提高性能。

指针存储位置

  • 裸指针存储在两个地方
    1. 控制块中,用于调用删除器释放资源。
    2. 每个 shared_ptr 实例里,为了快速访问,避免每次都去控制块读取。

总结

  • shared_ptr 背后有一个共享的控制块管理资源和计数。
  • 指针和删除器分开存储,减少内存开销,提高性能。
  • 支持类型转换生成新智能指针,方便多态管理。
    这个图展示了一个 shared_ptr(共享指针)的结构和引用计数机制,shared_ptr 是 C++ 中的智能指针,用于管理动态分配的对象的生命周期。它通过引用计数来确保对象在不再需要时被正确释放。

图解分析:

  1. shared_ptr 实例(p1, p2, p3)
    • 图中有三个 shared_ptr 实例:p1p2p3,分别指向类型为 TT2 的对象。
    • 每个 shared_ptr 包含两个指针:
      • T 指针:指向实际的对象(TT2)。
      • reference_container 指针:指向一个引用计数容器,用于管理引用计数。
  2. reference_container
    • 这是 shared_ptr 的核心部分,包含以下内容:
      • strong_ref_count:强引用计数,表示有多少个 shared_ptr 正在共享这个对象。
      • weak_ref_count:弱引用计数,表示有多少个 weak_ptr 引用这个对象。
      • T 指针:指向实际的对象。
      • deleter 指针:指向一个删除器函数,用于在引用计数为 0 时释放对象。
  3. 引用关系
    • p1p2 共享同一个 reference_container<T>,表示它们指向同一个对象(类型为 T)。这意味着它们的强引用计数(strong_ref_count)至少为 2。
    • p3 指向一个不同的对象(类型为 T2),因此它有自己的 reference_container<T2>

工作原理:

  • 当一个 shared_ptr 被创建时,它会创建一个 reference_container,并将 strong_ref_count 初始化为 1。
  • 当另一个 shared_ptr(如 p2)通过拷贝或赋值共享同一个对象时,reference_containerstrong_ref_count 增加(例如,从 1 增加到 2)。
  • 当一个 shared_ptr 被销毁时(例如超出作用域),strong_ref_count 减少。如果 strong_ref_count 减到 0,则调用 deleter 删除对象,并销毁 reference_container(如果 weak_ref_count 也为 0)。
  • weak_ptr 不会增加 strong_ref_count,但会增加 weak_ref_count,用于观察对象而不影响其生命周期。

图的含义总结:

  • p1p2 共享一个对象(T),它们的引用计数容器是同一个,强引用计数至少为 2。
  • p3 管理一个不同的对象(T2),有自己的引用计数容器。
  • 引用计数机制确保对象在所有 shared_ptr 不再使用时被正确释放。

关于 make_shared 优化,这里是它的核心思想:

在这里插入图片描述

  • make_shared 是一种创建 shared_ptr工厂函数,它一次性分配一块内存,同时存储对象本身和控制块(引用计数等信息)。
  • 这样做避免了像直接用 shared_ptr<T>(new T(...)) 那样,两次内存分配(一次为对象,一次为控制块)。
  • 减少内存碎片,提高分配效率和缓存局部性,性能更好。
  • 另外,make_shared 对异常安全也更友好,因为它保证对象和控制块一起分配,避免出现先分配对象再分配控制块时异常导致的资源泄漏。
    总结就是,make_shared 通过合并内存分配,优化了性能和安全性,是推荐创建shared_ptr的方式。

enable_shared_from_this 的作用是让一个对象可以安全地从自身获取一个 shared_ptr 智能指针

为什么需要它?

假设一个对象本来已经被一个 shared_ptr 管理了,但在对象内部你想创建一个指向自己的 shared_ptr,如果直接写:

std::shared_ptr<MyClass> p(this);

会导致引用计数错误,对象会被重复删除或者内存崩溃。

enable_shared_from_this 怎么帮忙?

通过继承 enable_shared_from_this,对象内部有一个隐藏的弱引用指向自己当前管理的 shared_ptr,调用 shared_from_this() 就能返回一个指向自己的、和已有的 shared_ptr 共享控制块的智能指针。
这样保证了引用计数正确,避免重复释放。

简单总结

  • 允许对象内部生成指向自己的 shared_ptr
  • 避免直接用裸指针创建 shared_ptr 导致的资源管理错误。
  • 常用于回调、事件处理、异步操作中,需要对象自己传递智能指针的场景。
    在这里插入图片描述

这是对智能指针使用的总结,核心要点如下:

  • 一定要用智能指针,避免手动 delete 带来的错误和内存泄漏风险。
  • 优先使用 unique_ptr(或者没有C++11时用 boost::scoped_ptr),因为它效率高且逻辑简单。
  • 当需要共享所有权时,使用 shared_ptr,适合多个指针共享同一个对象的场景。
  • 尽量使用 make_shared 创建 shared_ptr,这样更高效且避免手动写 new
  • 在适合的情况下,考虑使用对象内部引用计数(object-based reference counting),比如特定设计模式或框架中更合适。
    总之,这些建议帮你写出更安全、高效、易维护的现代C++代码。

相关文章:

  • windows安装和部署docker
  • 八.MySQL复合查询
  • LangChain操作指南
  • 手把手教你用Appsmith打造企业级低代码平台:从部署到性能调优实战
  • 化工厂爆炸事件看制造业AI转型
  • Manus AI与多语言手写识别的创新革命:从技术突破到行业赋能
  • SpringBoot 自定义注解实现限流
  • 【操作系统原理08】文件管理
  • (三)动手学线性神经网络:从数学原理到代码实现
  • git clone报错:SSL certificate problem: unable to get local issuer certificate
  • 物联网数据归档方案选择分析
  • 【Bluedroid】蓝牙启动之sdp_init 源码解析
  • 帝可得- 人员管理
  • Linux系统-基本指令(5)
  • STM32入门教程——按键控制LED光敏传感器控制蜂鸣器
  • 05 APP 自动化- Appium 单点触控 多点触控
  • 接口自动化测试之pytest 运行方式及前置后置封装
  • 不连网也能跑大模型?
  • YAML文件
  • NLP学习路线图(二十):FastText
  • 企业网站规划书范文/站长工具亚洲高清
  • 做网站免费搭建/石家庄百度seo代理
  • 网站二维码怎么做的/手机百度一下
  • 北京网站建设公/企业网络营销策略分析案例
  • 媒体公司网站模板/新媒体运营怎么自学
  • 做网站的得多少钱/微信朋友圈广告30元 1000次