shared_ptr智能指针源码剖析
来源:互联网 发布:mysql no字段 编辑:程序博客网 时间:2024/05/17 01:44
前几天有个人问了我一个问题: 如何将一个智能指针作为函数的返回值传递出来。当时这个问题一下子把我问倒了,后来经人提醒有一个叫shared_ptr的智能指针可以解决这个问题。
将shared_ptr作为函数返回值的代码是这样的:
01
#include <tr1/memory>
02
#include <stdio.h>
03
04
using
std::tr1::shared_ptr;
05
06
07
shared_ptr<
int
> ReturnSharedPtr()
08
{
09
shared_ptr<
int
> p(
new
int
(1000));
10
return
p;
11
}
12
13
int
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的实现
看了一下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
}
1
~__shared_count()
// nothrow
2
{
3
if
(_M_pi != 0)
4
_M_pi->_M_release();
5
6
COSTA_DEBUG_REFCOUNT;
7
}
1
virtual
void
2
_M_dispose()
// nothrow
3
{
4
COSTA_DEBUG_SHAREDPTR;
5
_M_del(_M_ptr);
6
}
下面是我写的一段简单的测试代码:
01
#include <stdio.h>
02
#include <tr1/memory>
03
04
using
std::tr1::shared_ptr;
05
06
07
shared_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
17
int
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
7
1000
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
05
using
std::tr1::shared_ptr;
06
07
shared_ptr<
int
> gp(
new
int
(2000));
08
09
shared_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
18
shared_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
27
void
* 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
45
int
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执行写操作是不安全的。
程序运行的结果如下:
01
Thread error. *gp=1000
02
Thread error. *gp=1000
03
Thread quit
04
Thread quit
05
Thread error. *gp=1000
06
Thread quit
07
Thread error. *gp=1000
08
Thread quit
09
Thread error. *gp=1000
10
Thread quit
11
Thread error. *gp=1000
12
Thread quit
13
Thread error. *gp=1000
14
Thread quit
15
Thread error. *gp=1000
16
Thread quit
17
Thread error. *gp=1000
18
Thread quit
19
Thread quit
01
costa@pepsi:~/test/cpp/shared_ptr$ ./b
02
Thread quit
03
Thread quit
04
Thread quit
05
Thread quit
06
Thread quit
07
Thread quit
08
Thread quit
09
Thread quit
10
Thread quit
11
Thread quit
跑了很多次都没有出错。说明共享引用计数的不同的shared_ptr执行swap是线程安全的。BOOST文档是可信的。
补充一个问题: 为什么shared_ptr可以作为STL标准容器的元素,而auto_ptr不可以
这篇文章小结一下:
1 shared_ptr是一个非常实用的智能指针。
2 shared_ptr的实现机制是在拷贝构造时使用同一份引用计数。
3 对同一个shared_ptr的写操作不是线程安全的。 对使用同一份引用计数的不同shared_ptr是线程安全的。
- shared_ptr智能指针源码剖析
- shared_ptr智能指针源码剖析
- shared_ptr智能指针源码剖析
- shared_ptr智能指针源码剖析
- shared_ptr智能指针源码剖析
- shared_ptr智能指针源码剖析
- shared_ptr智能指针源码剖析
- shared_ptr智能指针源码剖析
- shared_ptr智能指针源码剖析
- 智能指针之shared_ptr框架源码剖析
- 深入剖析智能指针 shared_ptr
- C++智能指针shared_ptr剖析
- 智能指针auto_ptr源码剖析
- 智能指针scoped_ptr源码剖析
- C++ 智能指针 shared_ptr
- 智能指针 shared_ptr
- Boost智能指针:shared_ptr
- 智能指针shared_ptr
- poj1442 最小路径覆盖
- nil NSNull NULL,用着。。。
- asp.net/c# 注册页实现激活邮箱验证
- 构建erlang的app
- 对于标量
- shared_ptr智能指针源码剖析
- 计算长方柱的表面积和体积
- exchange空间太满
- java 实验二
- 第一章作业
- (intermediate) UVA 最短路 10798 - Be wary of Roses
- java dom4j操作xml
- XMPP协议实现原理介绍
- 创建一个简单android百度地图应用