shared_ptr 引用计数相关问题

前言

智能指针是 C++11 增加的非常重要的特性,并且也是面试的高频考点,本文主要解释以下几个问题:

  • 引用计数是怎么共享的、怎么解决并发问题的
  • 资源释放时,控制块的内存释放吗
  • weak_ptr 怎么判断对象是否已经释放

文中源码用的是 LLVM libcxx-3.5.0,为了方便理解有部分修改,关于自定义删除器和内存池的部分都删掉了。

可以想象 std::shared_ptr 对象在内存中是这样(weak_ptr 内存布局与 shard_ptr 类似):

shared_ptr 控制块

shared_ptr

部分源码如下所示:

template <class _Tp>
class shared_ptr {
 public:
  using element_type = _Tp;
 private:
  element_type*        __ptr_;
  __shared_weak_count* __cntrl_;
 public:
  template<class _Yp>
  shared_ptr(_Yp* __p)
    : __ptr_(__p) {
      __cntrl_ = new __shared_weak_count();
  }

  shared_ptr(const shared_ptr& __r) 
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_) {
    if (__cntrl_ != nullptr) {
      __cntrl_->__add_shared();
    }
  }

  ~shared_ptr() {
    if (__cntrl_ != nullptr) {
       __cntrl_->__release_shared();
    }
  }
};

成员变量

element_type*        __ptr_;
__shared_weak_count* __cntrl_;

可以看到有两个成员变量,一个是指向资源的指针,另一个是指向控制块的指针。

class __shared_count {
 protected:
  long __shared_owners_;
 public:
  __shared_count(long __refs = 0) 
    : __shared_owners_(__refs) {}
};

class __shared_weak_count : private __shared_count {
  long __shared_weak_owners_;
 public:
  __shared_weak_count(long __refs = 0)
    : __shared_count(__refs),
      __shared_weak_owners_(__refs) {}
};

__shared_weak_count 又继承了 __shared_count,它们各有一个成员变量分别记录 shared_ptr 和 weak_ptr 的数量。

需要注意的是计数的初始值是 0,不是 1。

复制构造函数

shared_ptr(const shared_ptr& __r) 
  : __ptr_(__r.__ptr_),
    __cntrl_(__r.__cntrl_) {
  if (__cntrl_ != nullptr) {
    __cntrl_->__add_shared();
  }
}

复制的时候将指针指向同一个资源和同一个控制块,然后增加控制块的计数值,这样就能共享计数值了。那它是怎么保证并发安全的呢?

template <class T>
T increment(T& t) {
  return __sync_add_and_fetch(&t, 1);
}

void __shared_count::__add_shared() {
  increment(__shared_owners_);
}

void __shared_weak_count::__add_shared() {
  __shared_count::__add_shared();
}

它的实现非常简单,就是调用了原子函数将 __shared_owners_ 的值加 1。

可以看到它是通过使用原子函数来解决并发问题的,这样比使用锁的并发性要好一些。

析构函数

~shared_ptr() {
  if (__cntrl_ != nullptr) {
     __cntrl_->__release_shared();
  }
}

析构函数直接调用了一个 __release_shared 函数,它的代码如下:

template <class T>
T decrement(T& t)  {
  return __sync_add_and_fetch(&t, -1);
}

bool __shared_count::__release_shared() {
  if (decrement(__shared_owners_) == -1) {
    __on_zero_shared();
    return true;
  }
  return false;
}

void __shared_weak_count::__release_shared() {
  if (__shared_count::__release_shared()) {
    __release_weak();
  }
}

void __shared_weak_count::__release_weak() {
  if (decrement(__shared_weak_owners_) == -1) {
    __on_zero_shared_weak();
  }
}

在 __shared_weak_count::__release_shared() 内首先调用 __shared_count::__release_shared() 将 __shared_owners_ 的值减 1,如果没有其他 shared_ptr 指向该资源了,就释放资源占用的内存。然后又调用 __shared_weak_count::__release_weak() 将 __shared_weak_owners_ 的值减 1,如果也没有其他 weak_ptr 指向该资源了,就释放控制块占用的资源。

可以看出当最后一个 shared_ptr 析构时,若没有其他 weak_ptr 指向该资源控制块的内存会被释放;否则不会立即释放。那控制块什么时候释放呢?请看下文。

weak_ptr

部分源码如下所示:

template<class _Tp>
class weak_ptr {
 public:
  using element_type = _Tp;
 private:
  element_type*        __ptr_;
  __shared_weak_count* __cntrl_;
public:
  weak_ptr(shared_ptr<_Yp> const& __r)
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_) {
    if (__cntrl_) {
      __cntrl_->__add_weak();
    }
  }

  ~weak_ptr() {
    if (__cntrl_) {
      __cntrl_->__release_weak();
    }
  }

  shared_ptr<_Tp> lock() const {
    shared_ptr<_Tp> __r;
    __r.__cntrl_ = __cntrl_ ? __cntrl_->lock() : __cntrl_;
    if (__r.__cntrl_) {
      __r.__ptr_ = __ptr_;
    }
    return __r;
  }
};

成员变量

element_type*        __ptr_;
__shared_weak_count* __cntrl_;

成员变量与 shared_ptr 一样,也是一个指向资源的指针和一个指向控制块的指针。

构造函数

weak_ptr(shared_ptr<_Yp> const& __r)
  : __ptr_(__r.__ptr_),
    __cntrl_(__r.__cntrl_) {
  if (__cntrl_) {
    __cntrl_->__add_weak();
  }
}

构造函数很简单,就是将指针指向 shared_ptr 所指向的资源和控制块,然后调用 __add_weak 将弱引用计数加 1。

void __shared_weak_count::__add_weak() {
  increment(__shared_weak_owners_);
}

析构函数

~weak_ptr() {
  if (__cntrl_) {
    __cntrl_->__release_weak();
  }
}

析构函数就是调用 __shared_weak_count::__release_weak(),将 __shared_weak_owners_ 的值减 1,当没有其他 shared_ptr & weak_ptr 指向该资源时释放控制块,防止内存泄露。

lock

lock 是 weak_ptr 中很重要的函数,如果我们想使用 weak_ptr 指向的资源,必须先调用 lock() 函数获取一个 shared_ptr。

shared_ptr<_Tp> lock() const {
  shared_ptr<_Tp> __r;
  __r.__cntrl_ = __cntrl_ ? __cntrl_->lock() : __cntrl_;
  if (__r.__cntrl_) {
    __r.__ptr_ = __ptr_;
  }
  return __r;
}

__shared_weak_count* __shared_weak_count::lock()  {
  long object_owners = __shared_owners_;
  while (object_owners != -1) {
    if (__sync_bool_compare_and_swap(&__shared_owners_,
                       object_owners,
                       object_owners+1)) {
      return this;
    }
    object_owners = __shared_owners_;
  }
  return 0;
}

在 weak_ptr::lock() 中首先定义一个了 shared_ptr,然后为它设置指向控制块的指针,如果设置成功再设置指向资源的指针。

__shared_weak_count::lock() 中先获取当前 shared_ptr 的数量,只要指向的资源还存在(__shared_owners_ 不为 -1),就将计数加 1,然后返回控制块指针。

总结

引用计数是怎么共享的,怎么解决并发问题的?

通过使多个 shared_ptr 内部的 __cntrl_ 指向同一个控制块实现计数共享。

使用原子性函数来操作记录计数的变量来解决并发问题。

资源释放时,控制块的内存释放吗?

如果没有其他 weak_ptr 指向该资源,控制块的内存会释放;如果有其他 weak_ptr 指向该资源,那控制块的内存不会释放,由最后一个 weak_ptr 析构时释放。

weak_ptr 怎么判断对象是否已经释放?

使用 weak_ptr 时需要调用 lock() 函数升级成 shared_ptr,此时会检查 __shared_owners_ 看资源是否已释放。

参考资料

  • 《Effective Modern C++》
  • libcxx-3.5.0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/614499.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CSS的基础语法和常见的语法简单归纳

CSS CSS 是层叠样式表&#xff08;Cascading Style Sheets&#xff09;的缩写。它是一种用来控制网页样式和布局的标记语言。通过 CSS&#xff0c;可以定义网页中的元素&#xff08;如文字、图像、链接等&#xff09;的外观和排版方式&#xff0c;包括字体、颜色、大小、间距、…

【Android】Apk图标的提取、相同目录下相同包名提取的不同图标apk但是提取结果相同的bug解决

一般安卓提取apk图标我们有两种常用方法&#xff1a; 1、如果已经获取到 ApplicationInfo 对象&#xff08;假设名为 appInfo&#xff09;&#xff0c;那么我们获取方法为&#xff1a; appInfo.loadIcon(packageManager)// 返回一个 Drawable 对象2、 如果还没获取到 Applica…

静态分析-RIPS-源码解析记录-01

token流扫描重构部分&#xff0c;这一部分主要利用php的token解析api解析出来的token流&#xff0c;对其中的特定token进行删除、替换、对于特定的语法结构进行重构&#xff0c;保持php语法结构上的一致性 解析主要在lib/scanner.php中通过Tokenizer这个类来实现,也就是在main…

ICode国际青少年编程竞赛- Python-4级训练场-列表综合练习

ICode国际青少年编程竞赛- Python-4级训练场-列表综合练习 1、 Flyer[3].step(1) Flyer[7].step(2) Flyer[11].step(1) for i in range(4):Flyer[i * 2].step(1) Flyer[8].step(3)for i in range(3):Dev.turnRight()Dev.step(-5)2、 for i in range(5):Flyer[i5].step(Flyer[…

git 推送github 选https遇到登录 openSSH问题

使用https需要使用github令牌token作为密码&#xff0c; 使用SSH不需要登录。 还有一个问题&#xff1a; 创建github仓库后没有quick setup页面解决办法 千万不要点击任何多的操作&#xff01;&#xff01;&#xff01;输入仓库名&#xff0c;直接create&#xff01;&#x…

数据分析——业务指标分析

业务指标分析 前言一、业务指标分析的定义二、业务问题构建问题构建的要求 三、业务问题的识别在识别问题的阶段对于企业内部收益者的补充 四、竞争者分析竞争者分析的内容竞争者分析目的案例 五、市场机会识别好的市场机会必须满足的条件市场机会案例 六、风险控制数据分析师常…

多模态CLIP和BLIP

一、CLIP 全称为Contrastive Language-Image Pre-Training用于做图-文匹配&#xff0c;部署在预训练阶段&#xff0c;最终理解为图像分类器。 1.背景 以前进行分类模型时&#xff0c;存在类别固定和训练时要进行标注。因此面对这两个问题提出CLIP&#xff0c;通过这个预训练…

1.前端环境搭建

1.安装nodejs 因为我们开发Vue项目需要使用npm命令来创建和启动&#xff0c;安装node.js是为了获得这个命令&#xff0c;目前和使用node.js无关 下载地址&#xff1a;http://nodejs.cn/download/ 下载完之后安装&#xff0c;通过cmd查看是否安装成功 node --version2.创建项目…

老板必读:防数据泄露,保卫您的商业秘密

在信息技术高速发展的今天&#xff0c;数据泄露已成为所有企业都必须正视的风险。对于企业而言&#xff0c;数据不仅仅是一堆数字和信息的集合&#xff0c;更是企业的核心竞争力与商业秘密的载体。一旦数据泄露&#xff0c;不仅会导致经济损失&#xff0c;还可能使企业信誉受损…

如何解读 Web 自动化测试 Selenium API?

Web自动化测试是一种通过编写代码来模拟用户操作&#xff0c;并验证Web应用程序的功能和性能的技术。Selenium是一个流行的Web自动化测试工具&#xff0c;它提供了一组API来与Web浏览器进行交互。在本文中&#xff0c;我们将深入探讨Selenium API&#xff0c;并解释如何从零开始…

conan2 基础入门(01)-介绍

conan2 基础入门(01)-介绍 文章目录 conan2 基础入门(01)-介绍⭐什么是conan官网Why use Conan? ⭐使用现状版本情况个人知名开源企业 ⭐ConanCenter包中心github ⭐说明文档END ⭐什么是conan 官网 官网&#xff1a;Conan 2.0: C and C Open Source Package Manager 一句话来…

二维视觉尺寸测量简单流程

代码示例&#xff1a;opencv实战---物体尺寸测量_opencv尺寸测量精度-CSDN博客 灰度化 简化图像处理&#xff1a;灰度图像只包含亮度信息&#xff0c;不包含颜色信息&#xff0c;因此数据量比彩色图像小&#xff0c;处理起来更加简单和快速。这对于需要实时处理大量图像数据的场…

virtualbox下ubantu20.04版本实现与window的复制粘贴

1.建议开启双向 2.打开Ubuntu命令终端 快捷键 ctrialtt&#xff0c;具体在设置里面查看快捷键 3.卸载已有工具 sudo apt-get autoremove open-vm-tools4.安装 sudo apt-get install open-vm-tools-desktop5.记得sudo reboot重启 sudo reboot这里记得加上sudo&#xff0c;…

python软件测试Jmeter性能测试JDBC Request(结合数据库)的使用详解

这篇文章主要介绍了python软件测试Jmeter性能测试JDBC Request(结合数据库)的使用详解,文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值&#xff0c;需要的朋友们下面随着小编来一起学习学习吧 JDBC Request 这个 Sampler 可以向数据…

大模型时代,程序员如何卷?

最近在看电影《碟中谍7》&#xff0c;该片讲述了特工伊森亨特尝试与一个被称为智体的全能人工智能作战&#xff0c;其可以即时访问任何在线网络&#xff0c;他和他的团队成员试图找回控制人工智能智体所必需的两部分钥匙并将其摧毁的故事。 在剧中&#xff0c;智体是一个虚拟反…

Java --- 集合(2)--- 这篇文章让你学会如何使用List集合

本期文章来自黑马程序员以及Java入门到精通&#xff0c;希望各位大佬发现文章的瑕疵及时表出&#xff0c;另外也感谢您的收看。话不多说&#xff0c;直接进入正题...... 目录 一.List集合的使用&#xff1a; 二.三种遍历List方式&#xff1a; 首先还是给大家呈现这幅图&#x…

Coursera吴恩达深度学习专项课程01: Neural Networks and Deep Learning 学习笔记 Week 03

Neural Networks and Deep Learning Course Certificate 本文是学习 https://www.coursera.org/learn/neural-networks-deep-learning 这门课的笔记 Course Intro 文章目录 Neural Networks and Deep LearningWeek 03: Shallow Neural NetworksLearning Objectives Neural Ne…

短剧APP开发,为短剧市场提供更多活力

近年来&#xff0c;短剧一直是一个大热赛道&#xff0c;不仅各大视频平台刮起了一股短剧热潮&#xff0c;各大品牌也纷纷开始进军短剧市场。短剧作为当下的流量密码&#xff0c;深受各大短剧观众与创业者的关注。吸引了大量的资本、制作方涌入到市场中&#xff0c;短剧行业发展…

taro3兼容支付宝/微信小程序的自定义拖拽排序组件

描述&#xff1a;列表可以完成拖拽排序 此组件是根据支付宝原生文档改编成taro-vue3的形式&#xff0c;只保留了拖拽的部分&#xff0c;其他功能都去除了&#xff0c;测试下来可以兼容支付宝和微信小程序。 支付宝原生文档&#xff1a; https://opendocs.alipay.com/support/…

未来办公新方式--智能体与程序完美配合

Agent AI智能体的未来 工作中&#xff0c;有时候我们就像是在不停地踩着缝纫机&#xff0c;重复地做着那些单调乏味的任务&#xff0c;不仅耗时费力&#xff0c;还特别容易出错。可是&#xff0c;咱们现在可是生活在数字化时代啊&#xff01;这时候&#xff0c;Python编程语言…