Tars源码分析---智能指针的实现

2023-11-09

前言

Tars主要提供了三种类型的智能指针:TC_AutoPtr,TC_SharedPtr,TC_ScopedPtr。对智能指针的合适使用可以避免内存泄漏等问题。本文将分别对这三种智能指针在tars中的实现进行分析。

TC_AutoPtr
TC_HandleBaseT

在tars的设计中,所有想要使用TC_AutoPtr来管理的对象都需要继承TC_HandleBaseT。TC_HandleBaseT中核心是一个计数器:

    atomic_type   _atomic;

也就是说,所有使用TC_AutoPtr管理的对象都会隐含一个计数器在自己对象所属的内存中,该计数器记录了该对象被多少个不同的变量引用。当计数器值为0时,表示该对象对应的内存已经没有再被引用,此时就会被回收。因此TC_AutoPtr的核心实现就是对对象中的_atomic如何进行操作。下面我们介绍TC_AutoPtr相关函数的实现。

构造函数

TC_AutoPtr是提供了三个构造函数:

TC_AutoPtr(T* p = 0)
TC_AutoPtr(const TC_AutoPtr<Y>& r)
TC_AutoPtr(const TC_AutoPtr& r)

第一个构造函数表示可以直接使用原生对象指针对TC_AutoPtr进行初始化。
第二和第三个构造函数是使用其他智能指针进行构造。
这三个构造函数都做两件事情:

  1. 将原生指针放到TC_AutoPtr智能指针对象中。
  2. 增加使用智能指针的对象的引用计数

这里之所以要将原生对象指针放到TC_AutoPtr中,主要还是为了可以在TC_AutoPtr中方便操作该指针指向的对象的引用计数器。

赋值重载

实现智能指针的另一个需要完成的就是重载赋值运算符了。这主要是因为赋值可能会使得TC_AutoPtr本来指向的对象内存被回收,以及新指向的对象引用计数增加。下面看一下赋值运算符的重载实现。

TC_AutoPtr也是提供了三种赋值,可以分别支持对原生指针赋值,TC_AutoPtr对象赋值,其他类型的TC_AutoPtr对象赋值。

赋值运算主要也是做两件事:

  1. 修改TC_AutoPtr指向的对象指针
  2. 修改引用计数

其中,由于赋值涉及到=运算符左右两个对象,因此会引起两个对象的引用计数的修改。在赋值运算中唯一需要注意的是检查赋值的两个运算对象是否是相同的对象,还有一点是赋值时是有可能引起引用计数减为零,导致对象被析构的,这个动作会在TC_HandleBaseT的decRef()函数中完成。

析构函数

TC_AutoPtr的析构函数一般会在TC_AutoPtr对象走出当前作用域时被自动调用,而走出作用域以为着用户将不再使用这个TC_AutoPtr,因此它所指向的对象的引用计数应该减一

    ~TC_AutoPtr()
    {
        if(_ptr)
        {
            _ptr->decRef();
        }
    }

虽然简单,但这正是c++实现智能指针的前提。

TC_ScopedPtr

TC_ScopedPtr比TC_AutoPtr对对象的管理要严格的多:

    private:
        TC_ScopedPtr(const TC_ScopedPtr&);
        void operator=(const TC_ScopedPtr&);

它不允许拷贝构造和赋值。也就是说TC_ScopedPtr要求每一个对象只能有一个TC_ScopedPtr对它进行管理,当TC_ScopedPtr走出作用域时就会析构这个对象。当然,它不能避免用户进行如下的操作:

T* Ptr;
TC_ScopedPtr tmp1(Ptr);
TC_ScopedPtr tmp2(Ptr);

这样的话,就会导致Ptr被析构两次,出现未定义的行为。为了避免这种问题,在使用智能指针时应该避免同一个原生指针多次使用,亦即在构造智能指针之后,对原生指针的所有使用都应该基于这个智能指针才行,这样才符合语义。

TC_ScopedPtr虽然独占指针控制权,但是它支持控制权转移,这在TC_ScopedPtr的swap函数中有实现。

TC_SharedPtr

TC_SharedPtr也是使用引用计数进行对象管理。

TC_SharedPtr中有两个数据成员:

        T *m_px;
        detail::tc_shared_count_base *m_pn;

m_px是指向所管理的对象的指针,m_pn是一个管理结构。管理结构是继承自detail::tc_shared_count_base的tc_shared_count_impl_p或者tc_shared_count_impl_pd。下面对这三个类进行介绍

tc_shared_count_base

这个类就维护了一个数据成员:

tars::TC_Atomic m_use_count;

它是一个计数器。该类对外提供了dispose()接口供子类修改使用。dispose()是资源析构的核心函数。这也是tc_shared_count_impl_p和tc_shared_count_impl_pd不一样的地方。

tc_shared_count_impl_p

作为管理结构,除了继承自tc_shared_count_base的计数器,tc_shared_count_impl_p还保存了所管理的对象指针:

T *m_px;

它的任务就是实现dispose接口:

virtual void dispose()
{
    tc_checked_delete(m_px);
}

亦即简单将管理对象析构掉。

tc_shared_count_impl_pd

tc_shared_count_impl_pd和tc_shared_count_impl_p不同的是它为对象提供了一个删除器:

D m_deleter;

相应地,它通过调用删除器对对象进行处理:

virtual void dispose()  // no throw
{
     m_deleter(m_px);
}

用户可以通过向tc_shared_count_impl_pd注入自定义的删除器来控制对象的析构行为,比如默认删除器直接调用delete ptr,这不适用于数组。因此,如果是针对数组,则需要自定义删除器,调用delete [] ptr。

TC_EnableSharedFromThis

TC_SharedPtr要求所有需要使用该智能指针的类都继承TC_EnableSharedFromThis。这使得我们可以通过以下方式获得给定的一个对象指针的TC_SharedPtr:

T* ptr = new T;
TC_SharedPtr<T> tmp1 =  ptr;
TC_SharedPtr<T> tmp2 = ptr->sharedFromThis();

当然只有为原生指针构建了智能指针才能调用它的sharedFromThis()函数。这样就实现了共享对象的控制权。

构造函数

TC_SharedPtr提供了四个构造函数:

TC_SharedPtr();
explicit TC_SharedPtr(U *p)
TC_SharedPtr(U *p, D d)
TC_SharedPtr(const TC_SharedPtr& rhs)

第二个构造函数相对第一个构造函数多了一个d参数,这个参数标识的是前面说的删除器,用户可以通过这里给TC_SharedPtr注入删除器。这两个构造函数里还会有一行代码:

detail::tc_sp_enable_shared_from_this(this, p);

这主要是初始化指针p指向的对象的父类TC_EnableSharedFromThis中的数据成员,方便后面可以通过
p->sharedFromThis()获取该对象指针的TC_SharedPtr。

第四个构造函数会增加引用计数。

赋值重载

赋值重载是使用swap函数实现的,正如前面介绍第一个智能指针所说,赋值涉及到两个引用计数的变化。

TC_SharedPtr& operator=(const TC_SharedPtr& rhs)
{
     TC_SharedPtr(rhs).swap(*this);//TC_SharedPtr(rhs)构建一个局部变量
     return *this;
}

这里会为rhs构建一个临时TC_SharedPtr变量,因此rhs在构造函数中计数器会加1,然后调用swap函数,交换两者的计数器和对象指针,swap函数之后,等号左边的TC_SharedPtr保存的将是rhs对应的信息。swap执行完成之后,临时对象中存储的就是this指针指向的TC_SharedPtr,重载函数退出时,会调用该TC_SharedPtr的析构函数,计数器减1。因此一条语句就实现了两个计数器的变化。

总结

本文介绍了tars中实现的三种智能指针。TC_ScopedPtr比较简单,独占对象的控制权。TC_AutoPtr和TC_SharedPtr则使用引用计数控制对象的析构时机,它们都支持多个智能指针控制一个对象的生命周期。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Tars源码分析---智能指针的实现 的相关文章

  • C++课设-学生信息管理系统

    前言 上学期的一个简单的C 课设项目 代码在后面 附github项目链接 一 问题描述 建立学生信息数据 包括学号 姓名 性别 三科成绩 出生时间 年龄 必须计算得到 使用继承的方法构造至少3个类 即学生类 虚基类 一年级学生和二年级学生类
  • 输入一个矩阵,按照从外向里依顺序一次打印

    输入一个矩阵 按照从外向里以顺时针的顺序依次打印出每一个数字 例如 如果输入如下4 X 4矩阵 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1 2 3 4 8 12 16 15 14 13
  • Fedora 22安装后无法找到Realtek无线网卡的解决

    在主目录中建立一个文件夹 mkdir Reaktek 接着进入该目录 执行 git clone https github com lwfinger rtlwifi new git 会出现rtlwifi new的文件夹 进入文件夹 执行 ma
  • 编译安装LNMP全程实录

    此次是在CentOs 7 4上进行安装配置 先把编译环境配置好 yum y install gcc automake autoconf libtool make yum install gcc gcc c 准备一些软件的安装包 安装包 地址
  • 线性代数——求逆矩阵

    方法一 行列式分之一乘伴随矩阵 方法二 在右边拼个单位阵做初等行变换使得左边的原矩阵变为单位阵 这时右边即逆矩阵 抽象矩阵求逆 用公式AB E 利用计算技巧凑出公式 两边加E 提取公因式 没有公因式可提时利用隐形的E AA 1 因为E可看作
  • 【Deepin】 Deepin 系统安装教程

    安装过程 准备 准备足够的磁盘空间 下载 格式化 制作启动盘 安装 设置U盘启动项 根据引导安装 新建分区 设置 记录一下第N次安装Deepin系统的过程 准备 准备足够的磁盘空间 deepin用于生活日常的话 不需要太大的空间 我准备了4

随机推荐

  • 理性选择key-value Store

    前言 开源产品固然好 但是各种场景的数据需求确实多少有些差距 利用现有的软硬件资源面对现有的问题快速做出调整是才是数据库工程师的真正价值 综述 key value store由于本身实现不像成熟RDBMS那么复杂 换句话说开发周期不常 性能
  • AbstractQueuedSynchronizer之AQS

    一 是什么 抽象的队列同步器 是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石 通过内置的FIFO队列来完成资源获取线程的排队工作 并通过一个int类型的变量来表示持有锁的状态 官方说法 二 与AQS相关联的知识 1 位
  • el-Popconfirm 气泡确认框修改样式无效。使用popper-class自定义样式

    样式 需要在非scoped区域才会生效 注意需要添加唯一父节点 以免影响其他页面 html结构
  • CardView的具体使用方法

    今天主要是CardView的用法 CardView是在安卓5 0提出的卡片式控件 首先介绍一下它的配置 在gradle文件下添加依赖库 compile com android support cardview v7 22 2 1 其次介绍一
  • SQL SERVER中求上月、本月和下月的第一天和最后一天[转]

    上月的第一天 SELECT CONVERT CHAR 10 DATEADD month 1 DATEADD dd DAY GETDATE 1 GETDATE 111 SELECT DATEADD mm DATEDIFF mm 0 datea
  • 【SpringMVC】DispatcherServlet重要组件之一HandlerAdapter

    作用使用处理器干活的 共三个方法 package org springframework web servlet import javax servlet http HttpServletRequest import javax servl
  • PTA程序设计类实验辅助教学平台-基础编程题--JAVA--7.3 逆序的三位数

    import java util Scanner public class Main public static void main String args Scanner sc new Scanner System in int i sc
  • C二级考试第一套题

    一 1 题目 2 程序 考察范围 1 结构体的定义 关键字使用 2 链表节点的相关操作 3 malloc的应用 include
  • PyQt6 和 PyQt5 的差异

    PyQt6 和 PyQt5 的差异 PyQt6 是 PyQt5 的下一个版本 但两个版本的写法基本上其实大同小异 这篇教学会介绍 PyQt6 和 PyQt5 有何差异 快速预览 exec 改为 exec 方法的位置或名称改变 不需要高 DP
  • 行为型模式 - 迭代器模式iterator

    模式的定义与特点 迭代器模式 iterator Pattern 为的提是可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象 多个对象聚在一起形成的总体称之为聚集 聚集对象是能够包容一组对象的容器对象 迭代子模式将迭代逻辑封装到一个独立的子
  • 常见的智能指针

    智能指针其实是一个类模板 它与普通指针的区别在于他会自己释放内存空间 常见的智能指针有三种 unique ptr shared ptr weak ptr 他们都在头文件
  • Gradle到底是什么?

    Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具 它使用一种基于Groovy的特定领域语言来声明项目设置 而不是传统的XML 当前其支持的语言限于Java Groovy和Scala 计划未来将支持
  • java基础语法(二)

    3 java基础语法 3 1 注释 理解 注释是对代码的解释和说明文字 可以提高程序的可读性 因此在程序中添加必要的注释文字十分重要 Java中的注释分为三种 单行注释 单行注释的格式是使用 从 开始至本行结尾的文字将作为注释文字 这是单行
  • js正则表达式 必须包含数字、字母、指定特殊字符且不包含root

    js正则表达式要求 1 必须包含数字 大写字母 小写字母 特殊符号且长度在10到20之间 2 特殊符号包括 3 不包含root字符串 补充一下 有些朋友问到 最少包含2个大写字母 2个小写字母 2个数字 2个指定的特殊字符 长度10到20
  • FISCO BCOS(二)———配置及使用控制台

    1 准备依赖 安装java 推荐使用java 14 sudo apt install y default jdk 获取控制台并回到fisco目录 cd fisco curl LO https github com FISCO BCOS co
  • Spring Boot 多模块项目创建与配置

    最近在负责的是一个比较复杂项目 模块很多 代码中的二级模块就有9个 部分二级模块下面还分了多个模块 代码中的多模块是用maven管理的 每个模块都使用spring boot框架 之前有零零散散学过一些maven多模块配置的知识 但没自己从头
  • MATLAB 对多个数据自动寻峰/能谱图自动寻峰

    自动读取文件夹中的所有同类型数据文件 记录每个数据文件的峰值 并将所有峰值保存在excel中 数据是负的峰值类型 寻峰的逻辑是 设定一个阈值 0 002 挨个读取某一数据文件中的数据 如果当前数据大于阈值 则读取下一个数据 当当前数据小于阈
  • MANIFEST.MF文件作用及格式要求

    MANIFEST MF文件作用及格式要求 manifest mf文件格式如下 Manifest Version 1 0 Class Path xxx1 jar xxx2 jar xxx3 jar xxx4 jar xxx5 jar Main
  • 跨模态预训练迁移

    1 ViLD Zero Shot Detection via Vision and Language Knowledge Distillation code 2 OVR CNN Open Vocabulary Object Detectio
  • Tars源码分析---智能指针的实现

    前言 TC AutoPtr TC HandleBaseT 构造函数 赋值重载 析构函数 TC ScopedPtr TC SharedPtr tc shared count base tc shared count impl p tc sha