TarsCpp 组件 之 智能指针详解

Easter79
• 阅读 417

TarsCpp 组件 之 智能指针详解

在 C++ 中,内存管理是十分重要的问题,一不小心就会造成程序内存泄露,那么怎么避免呢?通过智能指针可以优雅地管理内存,让开发者只需要关注内存的申请,内存的释放则会被自动管理。在文章 开源微服务框架 TARS 之 基础组件(点击跳转)中已经简要介绍过,TARS 框架组件中没有直接使用 STL 库中的智能指针,而是实现了自己的智能指针。本文将会分别对 STL 库中的智能指针和 TarsCpp 组件中的智能指针进行对比分析,并详细介绍 TARS 智能指针的实现原理。

TarsCpp 组件 之 智能指针详解

• 智能指针

o 简介

o 引用计数原理

• STL 库中的智能指针

o shared_ptr

• TARS 智能指针 TC_AutoPtr 实现详解

o 原子计数类 std::atomic

o 智能指针基类 TC_HandleBase

o 智能指针模板类 TC_AutoPtr

o TC_AutoPtr 优势

TarsCpp 组件 之 智能指针详解

简介

在计算机程序中,泄露是常见的问题,包括内存泄露和资源泄露。其中资源泄露指的是系统的 socket、文件描述符等资源在使用后,程序不再需要它们时没有得到释放;内存泄露指的是动态内存在使用后,程序不再需要它时没有得到释放。

内存泄露会使得程序占用的内存越来越多,而很大一部分往往是程序不再需要使用的。在 C++ 程序中,内存泄露常见于我们使用了 new 或者 malloc 申请动态存储区的内存,却忘了使用 delete 或者 free 去释放内存,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

随着计算机应用需求的日益增加,应用的设计与开发日趋复杂,开发人员在开发过程中处理的变量也越来越多。如何有效进行内存分配和释放、防止内存泄漏逐渐成为开发者面临的重要难题。为了解决忘记手动释放内存造成的内存泄露问题,智能指针诞生了。

常见的智能指针的使用场景,包括类中的成员变量(指针型)和普通的变量(指针型)。智能指针可以实现指针指向对象的共享,而无需关注动态内存的释放。通用实现技术是引用计数(Reference count),下一部分会介绍,简单讲就是将一个计数器与类指向的对象相关联,跟踪有多少个指针指向同一对象,新增一个指针指向该对象则计数器 +1,减少一个则执行 -1。

引用计数原理

TarsCpp 组件 之 智能指针详解

引用计数是智能指针的一种通用实现技术,上图为大致流程,基本原理如下:

1.在每次创建类的新对象时,初始化指针并将引用计数置 1;

2.当对象作为另一对象的副本而创建时(复制构造函数),复制对应的指针并将引用计数 +1;

3.当对一个对象进行赋值时,赋值操作符 = 将左操作数所指对象的引用计数 -1,将右操作数所指对象的引用计数 +1;

4.调用析构函数数,引用计数 -1;

5.上述操作中,引用计数减至 0 时,删除基础对象;

STL 库中的智能指针 shared_ptr 和 TARS 智能指针都使用了该引用计数原理,后面会进行介绍。

TarsCpp 组件 之 智能指针详解

C++ 标准模板库 STL 中提供了四种指针 auto_ptr, unique_ptr, shared_ptr, weak_ptr。

auto_ptr 在 C++98 中提出,但其不能共享对象、不能管理数组指针,也不能放在容器中。因此在 C++11 中被摒弃,并提出 unique_ptr 来替代,支持管理数组指针,但不能共享对象。

shared_ptr 和 weak_ptr 则是 C++11 从标准库 Boost 中引入的两种智能指针。shared_ptr 用于解决多个指针共享一个对象的问题,但存在循环引用的问题,引入 weak_ptr 主要用于解决循环引用的问题。

接下来将详细介绍 shared_ptr,关于其它智能指针的更多信息和用法请读者自行查阅。

shared_ptr

shared_ptr 解决了在多个指针间共享对象所有权的问题,最初实现于 Boost 库中,后来收录于 C++11 中,成为了标准的一部分。shared_ptr 的用法如下

TarsCpp 组件 之 智能指针详解

上述代码的意思是 cp_sptrA 声明并赋值后,引用计数增加 1,cp_sptrA 销毁后引用计数 -1,但是没有触发 A 的析构函数,在 sprtA 销毁后,引用计数变为 0,才触发析构函数,实现内存的回收。执行结果如下

TarsCpp 组件 之 智能指针详解

shared_ptr 主要的缺陷是遇到循环引用时,将造成资源无法释放,下面给出一个示例:

TarsCpp 组件 之 智能指针详解

在上述例子中,我们首先定义了两个类 `A` 和 `B`:`A` 的成员变量是指向 `B` 的 `shared_ptr` 指针,`B` 的成员变量是指向 `A` 的 `shared_ptr` 指针。

然后我们创建了 `sptrB` 和 `sptrA` 两个智能指针对象,并且相互赋值。这会造成环形引用,使得 `A` 和 `B` 的析构函数都无法执行(可以通过 `cout` 观测),从而内存无法释放。当我们无法避免循环使用时,可以使用 `weak_ptr` 来解决,这里不再展开,感兴趣的读者可以自行查阅。

TarsCpp 组件 之 智能指针详解

TARS 诞生于 2008 年,当时 shared_ptr 还没有被收录到 STL 标准库中,因此自己实现了智能指针 TC_AutoPtr。TARS 的智能指针主要是对 auto_ptr 的改进,和 share_ptr 的思想基本一致,能够实现对象的共享,也能存储在容器中。与 shared_ptr 相比,TC_AutoPtr 更加轻量化,拥有更好的性能,本文后续会对比。

在 TARS 中,智能指针类 TC_AutoPtr 是一个模板类,支持拷贝和赋值等操作,其指向的对象必须继承自智能指针基类 TC_HandleBase ,包含了对引用计数的加减操作。计数采用的是 C++ 标准库 中的原子计数类型 std::atomic。

计数的实现封装在类 TC_HandleBase 中,开发者无需关注。使用时,只要将需要共享对象的类继承 TC_HandleBase,然后传入模板类 TC_AutoPtr 声明并构造对象即可,如下

TarsCpp 组件 之 智能指针详解

使用方式和 shared_ptr 相似,可以通过函数 getRef 获取当前计数,getRef 定义于 TC_HandleBase 类中。运行结果如下

TarsCpp 组件 之 智能指针详解

下面我们将自底向上介绍分析原子计数器 std::atomic、智能指针基类 TC_HandleBase 和智能指针模板类 TC_AutoPtr,并对 TC_AutoPtr 与 shared_ptr 的性能进行简单的对比测试。

原子计数类 std::atomic

std::atomic 在 C++11 标准库 中定义。std::atomic 是模板类,一个模板类型为 T 的原子对象中封装了一个类型为 T 的值。

TarsCpp 组件 之 智能指针详解

原子类型对象的主要特点就是从不同线程访问不会导致数据竞争(data race)。因此从不同线程访问某个原子对象是良性 (well-defined) 行为。而通常对于非原子类型而言,并发访问某个对象(如果不做任何同步操作)会导致未定义 (undifined) 行为发生。

C++11 标准库 std::atomic 提供了针对整型(integral)和指针类型的特化实现。下面是针对整型的特化实现的主要部分

TarsCpp 组件 之 智能指针详解

可以看到重载了大部分整型中常用的运算符,包括自增运算符 ++ 和自减运算符 --,可以直接使用自增或自减运算符直接对原子计数对象的引用值 +1 或 -1 。

智能指针基类 TC_HandleBase

TC_HandleBase 是 TARS 的智能指针基类,包含两个成员变量 _atomic 和 _bNoDelete,定义如下

TarsCpp 组件 之 智能指针详解

TC_HandleBase,为 TARS 智能指针模板类 TC_AutoPtr 提供引用计数的相关操作,增加计数和减少计数接口的相关代码如下

TarsCpp 组件 之 智能指针详解

可以看到,这里通过整型的原子计数类的对象 _atomic 实现引用计数,管理智能指针指向对象的引用计数。

智能指针模板类 TC_AutoPtr

TC_AutoPtr 的定义及其构造函数和成员变量如下述代码,成员变量 _ptr 是一个 T* 指针。构造函数初始化该指针并调用了 TC_HandleBase 成员函数 incRef 进行引用计数 +1,这要求类 T 是继承自 TC_HandleBase 的。

TarsCpp 组件 之 智能指针详解

TC_AutoPtr 在使用时可以简单的当作 STL 的 shared_ptr 使用,需要注意的是指向的对象必须继承自 TC_HandleBase(当然也可以自己实现智能指针基类,并提供与 TC_HandleBase 一致的接口),同时还要避免环形引用。下面我们看一下 TC_AutoPtr 其他接口的定义:

TarsCpp 组件 之 智能指针详解

TarsCpp 组件 之 智能指针详解

可以看到,这些接口都满足通用的引用计数规则。

• 构造函数 :除了初始化指针对象之外,将引用计数 `+1`;

• 拷贝构造函数:拷贝指针,引用计数 `+1`;

• 赋值操作符:拷贝指针,操作符右边的智能指针对应的引用计数 `+1`,左边的 `-1`;

• 析构函数:引用计数 `-1`;

TC_AutoPtr 优势

经过上述分析,可以发现 `TC_AutoPtr` 和 `shared_ptr` 在用法和功能上非常相似,都支持多个指针共享一个对象,支持存储在容器中,那 `TC_AutoPtr` 有什么优势呢?

相比于 STL 库中的 `shared_ptr`,`TC_AutoPtr` 更加轻量,具有更好的性能,我们可以通过如下简单的测试代码,通过测试二者构造和复制的耗时来衡量它们的性能

TarsCpp 组件 之 智能指针详解

TarsCpp 组件 之 智能指针详解

最后运行测试,输出的结果如下

TarsCpp 组件 之 智能指针详解

可以看出,二者的复制性能相近,而构造性能上, TC_AutoPtr 要比 shared_ptr 快一倍以上。

TarsCpp 组件 之 智能指针详解

本文主要介绍了 TARS 的智能指针组件 TC_AutoPtr 和 STL 的智能指针 shared_ptr。TC_AutoPtr 指向继承自智能指针基类 TC_HandleBase 的对象。TC_HandleBase 通过原子计数器 std::atomic 实现引用计数,确保引用计数是线程安全的。相比于 shared_ptr,TC_AutoPtr 拥有更好的性能;而 shared_ptr 有更加完善的功能。TarsCpp 框架已经支持 C++11,开发者能够根据业务具体需求自由选择。

TARS可以在考虑到易用性和高性能的同时快速构建系统并自动生成代码,帮助开发人员和企业以微服务的方式快速构建自己稳定可靠的分布式应用,从而令开发人员只关注业务逻辑,提高运营效率。多语言、敏捷研发、高可用和高效运营的特性使 TARS 成为企业级产品。


TARS基金会是Linux基金会下的非营利性、微服务基金会,致力于建设一个强大而灵活的微服务生态系统。无论你在哪个行业,无论你使用什么技术栈,这里能助你快速实现你的创意。

TarsCpp 组件 之 智能指针详解

TarsCpp 组件 之 智能指针详解


TarsCpp 组件 之 智能指针详解

TarsCpp 组件 之 智能指针详解

TarsCpp 组件 之 智能指针详解

点“在看”让TARS小姐姐变好看TarsCpp 组件 之 智能指针详解

TarsCpp 组件 之 智能指针详解

本文分享自微信公众号 - TARS星球(TarsCloud)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
PhoneGap设置Icon
参考:http://cordova.apache.org/docs/en/latest/config\_ref/images.html通过config.xml中的<icon标签来设置Icon<iconsrc"res/ios/icon.png"platform"ios"width"57"height"57"densi
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Easter79 Easter79
2年前
TarsCpp 组件之智能指针详解
!(https://ftp.bmp.ovh/imgs/2020/10/b7fb603e17fc6529.jpg)作者Eaton导语在C中,内存管理是十分重要的问题,一不小心就会造成程序内存泄露,那么怎么避免呢?通过智能指针可以优雅地管理内存,让开发者只需要关注内存的申请,内存的释放则会被自动管理。在文章开源微服务框
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k