首页 > 后端开发 > C++ > 正文

C++智能指针引用计数原理解析

P粉602998670
发布: 2025-09-22 14:22:01
原创
965人浏览过
引用计数通过共享所有权自动管理对象生命周期,解决内存泄漏与野指针问题;其核心是控制块中的强弱引用计数,配合原子操作确保线程安全;但需警惕循环引用、性能开销等挑战,可通过weak_ptr打破循环、优先使用make_shared优化分配,并根据所有权语义合理选择智能指针类型。

c++智能指针引用计数原理解析

C++智能指针中的引用计数,说白了,就是一种巧妙的内存管理机制,它让多个智能指针实例能够共同“拥有”同一个对象。当这个对象的所有者(也就是所有指向它的智能指针)都消失了,引用计数归零,对象也就自动被销毁了。它解决了手动管理内存时最头疼的内存泄漏和野指针问题,让程序员能更专注于业务逻辑,而不是整天提心吊胆地想着

delete
登录后复制

引用计数的核心在于为每一个被管理的对象维护一个计数器。每当有一个新的

std::shared_ptr
登录后复制
实例指向这个对象时,计数器就加一;每当一个
std::shared_ptr
登录后复制
实例不再指向这个对象(比如它被销毁了,或者被赋值了新的对象),计数器就减一。一旦计数器归零,这就意味着没有任何
std::shared_ptr
登录后复制
再关心这个对象了,此时,它就会被安全地销毁。这个计数器本身通常是原子操作的,以确保在多线程环境下也能正确地管理对象的生命周期。可以说,它就像一个隐形的管家,默默地为我们打理着对象的生老病死。

为什么需要引用计数,它解决了什么痛点?

在我看来,引用计数之所以成为现代C++不可或缺的一部分,因为它直接击中了C++传统内存管理的几个核心痛点。

最明显的,就是内存泄漏。想想看,我们用

new
登录后复制
分配了一块内存,然后可能因为程序逻辑复杂,或者异常发生,或者干脆就是粗心大意,忘了调用
delete
登录后复制
。结果呢?那块内存就成了“孤儿”,永远不会被回收,直到程序结束。如果这种情况频繁发生,系统资源就会被耗尽。

立即学习C++免费学习笔记(深入)”;

另一个让人头大的问题是野指针和重复释放。当多个原始指针指向同一个对象时,如果其中一个指针提前

delete
登录后复制
了对象,其他指针就变成了野指针,再去访问就会导致未定义行为甚至程序崩溃。更糟的是,如果多个指针都尝试
delete
登录后复制
同一个对象,那就会导致重复释放,这在操作系统层面是严重错误。

引用计数,特别是

std::shared_ptr
登录后复制
,就是为了解决这些问题而生的。它提供了一种共享所有权的语义。你不需要去判断什么时候该
delete
登录后复制
,也不用担心多个地方引用同一个对象时谁来负责销毁。只要还有
shared_ptr
登录后复制
指向它,对象就安然无恙;当最后一个
shared_ptr
登录后复制
消失,对象就自动、安全地被清理了。这就像是给对象设置了一个“生命维持系统”,只要还有“生命线”连接着,它就活着。这种自动化管理极大地提升了代码的健壮性和开发效率,让我可以把更多精力放在业务逻辑上,而不是与内存错误搏斗。

引用计数是如何在底层实现的?(以
std::shared_ptr
登录后复制
为例)

要理解

std::shared_ptr
登录后复制
的引用计数,就不得不提它的“幕后英雄”——控制块(Control Block)。这玩意儿通常是一个单独在堆上分配的小结构,它不直接存储你的对象,而是存储着关于你对象的一些管理信息。

一个典型的控制块至少会包含以下几个关键部分:

  1. 强引用计数(Strong Count):这就是我们常说的引用计数,一个整数(通常是
    std::atomic_long
    登录后复制
    ,为了线程安全)。它记录了有多少个
    std::shared_ptr
    登录后复制
    实例正在“拥有”这个对象。当它归零时,意味着对象可以被销毁了。
  2. 弱引用计数(Weak Count):这是为
    std::weak_ptr
    登录后复制
    准备的。
    weak_ptr
    登录后复制
    不拥有对象,所以它不会增加强引用计数,但它会增加弱引用计数。当强引用计数和弱引用计数都归零时,控制块本身才会被销毁。
  3. 指向被管理对象的指针:这是控制块真正指向你的
    T*
    登录后复制
    对象的地方。
  4. 自定义删除器(Custom Deleter):如果你在创建
    shared_ptr
    登录后复制
    时指定了特殊的删除逻辑(比如不是简单地
    delete
    登录后复制
    ,而是
    fclose
    登录后复制
    一个文件句柄),这个删除器就会存储在这里。
  5. 自定义分配器(Custom Allocator):如果你使用了自定义的内存分配器,相关信息也会在这里。

当一个

std::shared_ptr
登录后复制
被创建时(例如通过
std::make_shared
登录后复制
或从原始指针构造),如果它管理的内存还没有对应的控制块,就会先创建一个控制块。这个控制块的强引用计数被初始化为1,弱引用计数为0。

网易人工智能
网易人工智能

网易数帆多媒体智能生产力平台

网易人工智能 39
查看详情 网易人工智能
  • 复制构造或赋值一个
    std::shared_ptr
    登录后复制
    时,只是简单地将源
    shared_ptr
    登录后复制
    的控制块指针复制过来,然后将控制块里的强引用计数加一。
  • std::shared_ptr
    登录后复制
    的析构函数
    被调用时,它会先将控制块里的强引用计数减一。
    • 如果强引用计数减到零,那么它就会调用之前存储的删除器(默认是
      delete
      登录后复制
      )来销毁被管理的对象。
    • 接着,它会检查弱引用计数。如果此时强引用计数和弱引用计数都为零,那么控制块本身也会被销毁,释放掉它占用的内存。

这种分离的设计非常巧妙,它确保了即使所有

shared_ptr
登录后复制
都消失了,只要还有
weak_ptr
登录后复制
存在,控制块就不会立即销毁,
weak_ptr
登录后复制
仍然可以判断对象是否存活。这种机制在多线程环境下尤其重要,因为原子操作保证了计数的正确性,避免了竞态条件。

引用计数可能带来哪些问题和挑战,又该如何规避?

引用计数虽然强大,但它也不是银弹,在使用中也可能遇到一些问题和挑战,我们得学会如何规避它们。

首先,最经典也最令人头疼的就是循环引用(Circular References)。这是

shared_ptr
登录后复制
最著名的陷阱。当两个或多个对象通过
shared_ptr
登录后复制
相互持有对方的引用时,它们的强引用计数永远不会降到零,即使外部已经没有其他
shared_ptr
登录后复制
指向它们了,它们也无法被销毁,最终导致内存泄漏。比如,对象A有一个指向B的
shared_ptr
登录后复制
,同时对象B也有一个指向A的
shared_ptr
登录后复制
。它们互相依赖,谁也无法释放对方,形成了一个“死锁”般的循环。

规避方法:对于循环引用,解决方案通常是引入

std::weak_ptr
登录后复制
weak_ptr
登录后复制
是一种“非拥有”的智能指针,它不会增加对象的强引用计数。当你需要打破循环时,让其中一个对象持有另一个对象的
weak_ptr
登录后复制
而不是
shared_ptr
登录后复制
。这样,当外部对这两个对象的强引用都消失后,即使它们之间有
weak_ptr
登录后复制
的相互引用,强引用计数也能归零,对象就能被正常销毁了。在使用
weak_ptr
登录后复制
时,你需要通过
lock()
登录后复制
方法尝试获取一个
shared_ptr
登录后复制
,如果对象已经不存在了,
lock()
登录后复制
会返回一个空的
shared_ptr
登录后复制

其次,是性能开销

shared_ptr
登录后复制
的引用计数操作(增减)通常需要原子操作来保证多线程安全,这比普通的非原子操作要慢一些。此外,控制块通常需要单独的堆内存分配(除非使用
std::make_shared
登录后复制
),这也增加了额外的内存分配和访问开销。对于性能敏感的场景,这些开销是需要考虑的。

规避方法

  • 优先使用
    std::make_shared
    登录后复制
    make_shared
    登录后复制
    能够一次性分配对象和控制块所需的内存,减少了一次堆分配,并且通常能更好地利用缓存,提高性能。
  • 理解所有权语义:如果对象是独占所有权(只有一个地方拥有并负责销毁它),那么
    std::unique_ptr
    登录后复制
    是更好的选择。
    unique_ptr
    登录后复制
    几乎没有运行时开销,因为它不需要引用计数。只有在确实需要共享所有权时,才使用
    std::shared_ptr
    登录后复制
  • 避免不必要的
    shared_ptr
    登录后复制
    拷贝
    :每次拷贝都会导致原子操作。如果只是临时访问对象,可以考虑传递
    shared_ptr
    登录后复制
    的引用,或者在确保对象存活的情况下,直接传递原始指针。
  • 性能分析:如果怀疑
    shared_ptr
    登录后复制
    性能瓶颈,进行详细的性能分析是必要的,不要过早优化。

最后,引用计数也并非适用于所有资源管理场景。它主要针对的是堆内存对象的生命周期管理。对于文件句柄、网络连接、互斥锁等其他类型的资源,虽然

shared_ptr
登录后复制
可以配合自定义删除器来管理,但
std::unique_ptr
登录后复制
配合自定义删除器通常是更轻量和更合适的选择,因为它明确了资源的独占性。

规避方法:在选择智能指针时,始终先思考资源的所有权语义:是独占(

unique_ptr
登录后复制
),还是共享(
shared_ptr
登录后复制
),还是非拥有观察者(
weak_ptr
登录后复制
或原始指针)?根据实际需求选择最合适的智能指针,这能让你的代码更清晰、更高效、也更安全。理解它们的优缺点和适用场景,是写出高质量C++代码的关键。

以上就是C++智能指针引用计数原理解析的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号