shared_ptr智能指针源码剖析

前几天有个人问了我一个问题: 如何将一个智能指针作为函数的返回值传递出来。当时这个问题一下子把我问倒了,后来经人提醒有一个叫shared_ptr的智能指针可以解决这个问题。

将shared_ptr作为函数返回值的代码是这样的:

01#include <tr1/memory>
02#include <stdio.h>
03 
04using std::tr1::shared_ptr;
05 
06 
07shared_ptr<int> ReturnSharedPtr()
08{
09    shared_ptr<int> p(new int(1000));
10    return p;
11}
12 
13int main()
14{
15    shared_ptr<int> p1 = ReturnSharedPtr();
16    printf("%d\n", *p1);
17    return 0;
18}


在g++4.3版本以上编译通过。shared_ptr头文件的位置有点古怪,在我的DEBIAN(squeeze)机器上的这个地方:/usr/include/c++/4.4/tr1/shared_ptr.h,所以使用时要

1#include <tr1/memory>
而且shared_ptr被包装在std::tr1这个名字空间内。所以使用的时候和一般的stl模板的方式不大一样, 要using std::tr1这个namespace。也可以用一个编译器选项去掉这种古怪的定义方法, 具体是那个选项我查不到了。请了解的人帮忙指正一下。


shared_ptr的实现

看了一下stl的源码,shared_ptr的实现是这样的:  shared_ptr模板类有一个__shared_count类型的成员_M_refcount来处理引用计数的问题。__shared_count也是一个模板类,它的内部有一个指向Sp_counted_base_impl类型的指针_M_pi。所有引用同一个对象的shared_ptr都共用一个_M_pi指针。

当一个shared_ptr拷贝复制时, _M_pi指针调用_M_add_ref_copy()函数将引用计数+1。 当shared_ptr析构时,_M_pi指针调用_M_release()函数将引用计数-1。 _M_release()函数中会判断引用计数是否为0. 如果引用计数为0, 则将shared_ptr引用的对象内存释放掉。


1__shared_count(const __shared_count& __r)
2      : _M_pi(__r._M_pi) // nothrow
3      {   
4    if (_M_pi != 0)
5      _M_pi->_M_add_ref_copy();
6      COSTA_DEBUG_REFCOUNT;
7      }

这是__shared_count拷贝复制时的代码。首先将参数__r的_M_pi指针赋值给自己, 然后判断指针是否为NULL, 如果不为null 则增加引用计数。COSTA_DEBUG_REFCOUNT和COSTA_DEBUG_SHAREDPTR是我为了打印引用计数的调试代码,会打印文件行号和当前引用计数的值。


1#define COSTA_DEBUG_REFCOUNT fprintf(stdout,"%s:%d costaxu debug refcount: %d\n", __FILE__,__LINE__,_M_pi->_M_get_use_count());
2 
3 
4#define COSTA_DEBUG_SHAREDPTR fprintf(stdout,"%s:%d costaxu debug \n", __FILE__,__LINE__);



01__shared_count&
02      operator=(const __shared_count& __r) // nothrow
03      {   
04    _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
05    if (__tmp != _M_pi)
06      {   
07        if (__tmp != 0)
08          __tmp->_M_add_ref_copy();
09        if (_M_pi != 0)
10          _M_pi->_M_release();
11        _M_pi = __tmp;
12      }
13      COSTA_DEBUG_REFCOUNT;
14    return *this;
15      }
这是__share_count重载赋值操作符的代码。 首先,判断等号左右两边的__share_count是否引用同一个对象。如果引用同一个对象(__tmp==_M_pi),那么引用计数不变,什么都不用做。如果不是的话,就把等号左边的share_ptr的引用计数-1,将等号右边的引用计数+1 。例如: 有两个shared_ptr p1和p2, 运行p1= p2 。 假如p1和p2是引用同一个对象的,那么引用计数不变。 如果p1和p2是指向不同对象的,那么p1所指向对象的引用计数-1, p2指向对象的引用计数+1。




1~__shared_count() // nothrow
2      {
3    if (_M_pi != 0)
4      _M_pi->_M_release();
5 
6      COSTA_DEBUG_REFCOUNT;
7      }
上面是__share_count的析构函数, 其实析构函数只是调用了_M_pi的_M_release这个成员函数。_M_release这个函数,除了会将引用计数-1之外,还会判断是否引用计数为0, 如果为0就调用_M_dispose()函数。 _M_dispose函数会将share_ptr引用的对象释放内存。




1virtual void
2      _M_dispose() // nothrow
3      {
4          COSTA_DEBUG_SHAREDPTR;
5          _M_del(_M_ptr);
6      }
_M_del是在构造_M_pi时候就初始化好的内存回收函数, _M_ptr就是shared_ptr引用的对象指针。


下面是我写的一段简单的测试代码:


01#include <stdio.h>
02#include <tr1/memory>
03 
04using std::tr1::shared_ptr;
05 
06 
07shared_ptr<int> ReturnSharedPtr()
08{
09    shared_ptr<int> p(new int(1000));
10    shared_ptr<int> p2(p);
11    shared_ptr<int> p3=p;
12    shared_ptr<int> p4;
13    p4=p2;
14    return p;
15}
16 
17int main()
18{
19    shared_ptr<int> p1 = ReturnSharedPtr();
20    printf("%d\n", *p1);
21    return 0;
22}

下面是运行结果:

shared_ptr.h 169行是__shared_count拷贝构造时增加引用计数,184行是__shared_count赋值操作,161行是__share_count的析构时减少引用计数, 79行是释放引用对象的内存。



1/usr/include/c++/4.4/tr1/shared_ptr.h:169 costaxu debug refcount: 2
2/usr/include/c++/4.4/tr1/shared_ptr.h:169 costaxu debug refcount: 3
3/usr/include/c++/4.4/tr1/shared_ptr.h:184 costaxu debug refcount: 4
4/usr/include/c++/4.4/tr1/shared_ptr.h:161 costaxu debug refcount: 3
5/usr/include/c++/4.4/tr1/shared_ptr.h:161 costaxu debug refcount: 2
6/usr/include/c++/4.4/tr1/shared_ptr.h:161 costaxu debug refcount: 1
71000
8/usr/include/c++/4.4/tr1/shared_ptr.h:79 costaxu debug
9/usr/include/c++/4.4/tr1/shared_ptr.h:161 costaxu debug refcount: 0


shared_ptr线程安全性问题

关于shared_ptr的线程安全性。查了一些网上的资料,有的说是安全的,有的说不安全。引用CSDN上一篇比较老的帖子, 它是这样说的:

Boost 文档对于 shared_ptr 的线程安全有一段专门的记述,内容如下:
shared_ptr objects offer the same level of thread safety as built-in types. A shared_ptr instance can be "read" (accessed using only const operations) simultaneously by multiple threads. Different shared_ptr instances can be "written to" (accessed using mutable operations such as operator= or reset) simultaneosly by multiple threads (even when these instances are copies, and share the same reference count underneath.)
Any other simultaneous accesses result in undefined behavior.
翻译为中文如下:
shared_ptr 对象提供与内建类型一样的线程安全级别。一个 shared_ptr 实例可以同时被多个线程“读”(仅使用不变操作进行访问)。 不同的 shared_ptr 实例可以同时被多个线程“写入”(使用类似 operator= 或 reset 这样的可变操作进行访问)(即使这些实 例是拷贝,而且共享下层的引用计数)。
任何其它的同时访问的结果会导致未定义行为。”

这几句话比较繁琐,我总结一下它的意思:

1 同一个shared_ptr被多个线程“读”是安全的。

2 同一个shared_ptr被多个线程“写”是不安全的。

3 共享引用计数的不同的shared_ptr被多个线程”写“ 是安全的。

如何印证上面的观点呢?

其实第一点我觉得比较多余。因为在多个线程中读同一个对象,在正常情况下不会有什么问题。

所以问题就是:如何写程序证明同一个shared_ptr被多个线程"写"是不安全的?

我的思路是,在多个线程中同时对一个shared_ptr循环执行两遍swap。 shared_ptr的swap函数的作用就是和另外一个shared_ptr交换引用对象和引用计数,是写操作。执行两遍swap之后, shared_ptr引用的对象的值应该不变。

程序如下:


01#include <stdio.h>
02#include <tr1/memory>
03#include <pthread.h>
04 
05using std::tr1::shared_ptr;
06 
07shared_ptr<int> gp(new int(2000));
08 
09shared_ptr<int>  CostaSwapSharedPtr1(shared_ptr<int> & p)
10{
11    shared_ptr<int> p1(p);
12    shared_ptr<int> p2(new int(1000));
13    p1.swap(p2);
14    p2.swap(p1);
15    return p1;
16}
17 
18shared_ptr<int>  CostaSwapSharedPtr2(shared_ptr<int> & p)
19{
20    shared_ptr<int> p2(new int(1000));
21    p.swap(p2);
22    p2.swap(p);
23    return p;
24}
25 
26 
27void* thread_start(void * arg)
28{
29    int i =0;
30    for(;i<100000;i++)
31    {
32        shared_ptr<int> p= CostaSwapSharedPtr2(gp);
33        if(*p!=2000)
34        {
35            printf("Thread error. *gp=%d \n", *gp);
36            break;
37        }
38    }
39    printf("Thread quit \n");
40    return 0;
41}
42 
43 
44 
45int main()
46{
47    pthread_t thread;
48    int thread_num = 10, i=0;
49    pthread_t* threads = new pthread_t[thread_num];
50    for(;i<thread_num;i++)
51        pthread_create(&threads[i], 0 , thread_start , &i);
52    for(i=0;i<thread_num;i++)
53        pthread_join(threads[i],0);
54    delete[] threads;
55    return 0;
56}



这个程序中我启了10个线程。每个线程调用10万次 CostaSwapSharedPtr2函数。 在CostaSwapSharePtr2函数中,对同一个share_ptr全局变量gp进行两次swap(写操作), 在函数返回之后检查gp的值是否被修改。如果gp值被修改,则证明多线程对同一个share_ptr执行写操作是不安全的。

程序运行的结果如下:

01Thread error. *gp=1000
02Thread error. *gp=1000
03Thread quit
04Thread quit
05Thread error. *gp=1000
06Thread quit
07Thread error. *gp=1000
08Thread quit
09Thread error. *gp=1000
10Thread quit
11Thread error. *gp=1000
12Thread quit
13Thread error. *gp=1000
14Thread quit
15Thread error. *gp=1000
16Thread quit
17Thread error. *gp=1000
18Thread quit
19Thread quit
10个线程有9个出错。证明多线程对同一个share_ptr执行写操作是不安全的。我们在程序中,如果不运行CostaSwapSharedPtr2, 改成运行CostaSwapSharedPtr1呢?  CostaSwapSharedPtr1和CostaSwapSharedPtr2的区别在于, 它不是直接对全局变量gp进行写操作,而是将gp拷贝出来一份再进行写操作。运行的结果如下:
01costa@pepsi:~/test/cpp/shared_ptr$ ./b
02Thread quit
03Thread quit
04Thread quit
05Thread quit
06Thread quit
07Thread quit
08Thread quit
09Thread quit
10Thread quit
11Thread quit

跑了很多次都没有出错。说明共享引用计数的不同的shared_ptr执行swap是线程安全的。BOOST文档是可信的。

补充一个问题: 为什么shared_ptr可以作为STL标准容器的元素,而auto_ptr不可以

这篇文章小结一下:

1 shared_ptr是一个非常实用的智能指针。

2 shared_ptr的实现机制是在拷贝构造时使用同一份引用计数。

3 对同一个shared_ptr的写操作不是线程安全的。 对使用同一份引用计数的不同shared_ptr是线程安全的。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机用户win7修改不,Win7电脑时间改不了的解决方法

在平时的工作中&#xff0c;我们经常会碰到一些问题&#xff0c;其中最常见的就是电脑时间改不了了。其实遇到这个问题很好解决的&#xff0c;但是很多用户都不太懂&#xff0c;为此小编赶紧整理了Win7电脑时间改不了的解决方法来帮助大家&#xff0c;大家赶紧看看吧&#xff0…

win7蓝屏0x000000f4修复_注意:关于近期多数电脑蓝屏的处理和预防方法

近期出现部分用户电脑因win7操作系统服役期结束&#xff0c;更新操作系统补丁导致系统蓝屏&#xff0c;错误代码0X000000F4的现象(如下图所示)&#xff1a;在此提醒&#xff0c;可尝试按照以下方法进行处理并设置。如果还没有出现蓝屏的客户&#xff0c;在系统开机时如出现如下…

用yum安装完mysql后没有mysqld的问题

在Centos中用命令 yum install mysql安装数据库&#xff0c;但装完后运行mysqld启动mysql的时候提示找不到&#xff0c;通过 find / | grep mysqld 也没找到mysqld的目录&#xff0c;后来在Google上搜索下&#xff0c;才知道用yum安装时候mysql也有三个参数的。 yum install my…

python中for和while可以有else_Python 中的for,if-else和while语句

for循环功能for 循环是一种迭代循环机制&#xff0c;迭代即重复相同的逻辑操作&#xff0c;每次的操作都是基于上一次的结果而进行的。并且for循环可以遍历任何序列的项目&#xff0c;如一个列表或者一个字符串语法for 循环的一般格式如下&#xff1a;for in 注释&#xff1a;v…

am335x修改sd卡cd管脚

任务&#xff1a;修改SD卡CD管脚&#xff0c;CD管脚是用来给系统通知SD卡的插入与拔出消息的&#xff0c;tq3358默认用的是 spi0_cs1(GPIO0_6)&#xff0c;现在要改为GPIO1_16 1. 查看原理图核心板原理图 MMC0的SDCD接的是GPIO0_6。 2. 查看 arch\arm\mach-omap2\mux33…

未来计算机论文1500,致未来的自己作文1500字

最了解自己的自己&#xff1a;展信乐&#xff01;此信寄予你&#xff0c;许只是无处发泄无聊的情绪吧&#xff0c;你若收到&#xff0c;必然会懂。不知时隔三年&#xff0c;你是否还能记得&#xff0c;那个多少还有些单纯的我&#xff1f;在过去的三年里&#xff0c;那个冒冒失…

sql server数据库还原方法

把数据库的备份文件放到服务器的任意目录下先&#xff0c; 然后按下面的步骤做。 如何从备份设备还原备份&#xff08;企业管理器&#xff09; 从备份设备还原备份 展开服务器组&#xff0c;然后展开服务器。 展开"数据库"文件夹&#xff0c;右击数据库&#xff0c;指…

cad插件_抖音最火CAD插件教程汇总

左下角阅读原文看CAD视频好课推荐&#xff1a;1、CAD2014&#xff1a;点击查看 2、室内CAD&#xff1a;点击查看 3、CAD2019&#xff1a;点击查看4、CAD2018&#xff1a;点击查看5、Bim教程&#xff1a;点击查看6、室内手绘&#xff1a;点击查看7、CAD三维&#xff1a;点击查看…

在内存中建立文件_磁盘与文件,搞懂它

说一说计算机中的非常重要的两个东西磁盘和文件。搞清楚这两个东西有利于我们理解高级语言中关于I/O流操作的设计。它就像一把大杀器一样&#xff0c;无往而不利。想一想&#xff0c;磁盘作为一个电脑中的硬件设备&#xff0c;操作系统是如何管理磁盘设备的&#xff1f;文件其实…

libxml2交叉编译问题及解决办法

libxml2安装中出现的错误:cannot remove libtoolT: No such file or directory解决方法: 修改configure文件 $ vim configure删除这一行: $RM "$cfgfile" 重新再运行 $ ./configure # ./configure --hostarm-linux --buildi386-linux --targetarm --prefix/u…

win7 dos窗口输入命令必须加后缀问题,例如 java必须输java.exe

1、描述&#xff1a; 当java的环境变量设置正确后依然无法java &#xff0c;只有添加java.exe后方可运行问题 2、位置&#xff1a;win 环境变量中的pathtext 3、解决方案&#xff1a;恢复默认的值即可(或google参考别人的)转载于:https://www.cnblogs.com/ki-tom/archive/2013/…

平板电脑应用_什么是机房巡检AI机器人?工业平板电脑的应用如何体现

机房巡检AI机器人&#xff0c;顾名思义&#xff0c;它的主要工作是在机房里做巡逻检查&#xff0c;是巡检机器人类别下的一个型号&#xff0c;类似的还有电站巡检AI机器人&#xff0c;铁路巡检AI机器人等&#xff0c;总之&#xff0c;它们的设计研发就是为了某个特定的工作环境…

东北师范大学计算机科学与技术录取分数线,东北师范大学计算机科学与技术专业2015年在河南理科高考录取最低分数线...

类似问题答案东北师范大学计算机类专业2016年在河南理科高考录取最低分数线学校 地 区 专业 年份 批次 类型 分数 东北师范大学 河南 计算机类 2016 一批 理科 557 东北师范大学 河南 计算机类 2016 一批 理科 557 学校 地 区 专业 年份 批次 类型 分数 东北师范大学 河南 计算…

二叉树的深度_十七:二叉树的最小深度

二叉树的最小深度&#xff1a;从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径&#xff0c;最短路径的长度为树的最小深度。算法一/** * description 二叉树最小深度 * param {*} root 二叉树 */function binaryTreeMinDepth(root) { // 节点不存在时返回长度…

reactor设计模式 1

Reactor这个词译成汉语还真没有什么合适的&#xff0c;很多地方叫反应器模式&#xff0c;但更多好像就直接叫reactor模式了&#xff0c;其实我觉着叫应答者模式更好理解一些。通过了解&#xff0c;这个模式更像一个侍卫&#xff0c;一直在等待你的召唤&#xff0c;或者叫召唤兽…

LinkedBlockingQueue应用实例

并发库中的BlockingQueue是一个比较好玩的类&#xff0c;顾名思义&#xff0c;就是阻塞队列。该类主要提供了两个方法put()和take()&#xff0c;前者将一个对象放到队列中&#xff0c;如果队列已经满了&#xff0c;就等待直到有空闲节点&#xff1b;后者从head取一个对象&#…

苹果怎么付费购买内存_【苹果手机多长时间清理一次内存,怎么清理?】

一、微信缓存微信作为每天使用频率最多的软件&#xff0c;是需要重点清理的对象。操作&#xff1a;我—设置—通用—清理微信存储空间—查看微信存储空间&#xff0c;选择联系人进行清理。二、短信现在很少有人会用短信联系&#xff0c;一般都是一些垃圾广告&#xff0c;我们可…

用计算机计算2的31次方,2的31次方,用什么方法可以最快算出来呢

请告诉我过程和怎么算出来的好吗? 2-2的2次方....-2的19次方 2的20次方2 - 2^2 - 2^3 - 2^4 - 。。。。 -2^19 2^202 2^20 - ( 2^2 2^3 2^4 。。。。 2^19)学过等比数列吗&#xff0c;学过就用公式Sna1(1-q^n)/(1/q)(a1-q*an)/(1-q)2^2 2^3 2^4 。 。。。 2^19 2^2(1-2^18)/(…

发票管理软件_财务人员都在用的这款发票管理软件,真的值得购买么?

(1)电子凭证和纸质会计凭证具有同等效力&#xff1b;(2)报销管理也需要经办、审核、审批流程&#xff0c;且能防止重复入账&#xff1b;(3)以电子凭证纸质版入账留档保存的&#xff0c;必须同时保存电子档。以及重复繁琐的发票查验工作&#xff0c;把最近处于风口浪尖的电子发票…

SDL以及扩展库的交叉编译过程简介

下面我介绍一下SDL以及SDL的扩展库在arm11上的交叉编译 在这里我将SDL 交叉编译的相关都安装在/opt/arm目录下 相关简介&#xff1a; SDL官方网站 http://www.libsdl.org/ SDL编译移植(Up-teach6410平台) 系统环境&#xff1a;linux 移植环境&#xff1a;arm11 编译工具链&…