shared_ptr的实现
看了一下stl的源码,shared_ptr的实现是这样的: shared_ptr模板类有一个__shared_count类型的成员,_M_refcount来处理引用计数的问题。__shared_count也是一个模板类,它的内部有一个指针_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引用的对象内存释放掉。
_M_pi->_M_add_ref_copy();
COSTA_DEBUG_REFCOUNT;
}
这是__shared_count拷贝复制时的代码。首先将参数__r的_M_pi指针赋值给自己, 然后判断指针是否为NULL, 如果不为null 则增加引用计数。COSTA_DEBUG_REFCOUNT和COSTA_DEBUG_SHAREDPTR是
#define COSTA_DEBUG_REFCOUNT fprintf(stdout,"%s:%d costaxu debug refcount: %d ", __FILE__,__LINE__,_M_pi->_M_get_use_count());
#define COSTA_DEBUG_SHAREDPTR fprintf(stdout,"%s:%d costaxu debug ", __FILE__,__LINE__);
我为了打印引用计数的调试代码,会打印文件行号和当前引用计数的值。
__shared_count& operator=(const__shared_count& __r) // nothrow
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if(__tmp != _M_pi)
{
if(__tmp != 0)
__tmp->_M_add_ref_copy();
if(_M_pi != 0)
_M_pi->_M_release();
_M_pi = __tmp;
}
COSTA_DEBUG_REFCOUNT;
return*this;
}
这是__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。
~__shared_count()// nothrow
{
if(_M_pi != 0)
_M_pi->_M_release();
COSTA_DEBUG_REFCOUNT;
}
上面是__share_count的析构函数, 其实析构函数只是调用了_M_pi的_M_release这个成员函数。_M_release这个函数,除了会将引用计数-1之外,还会判断是否引用计数为0, 如果为0就调用_M_dispose()函数。 _M_dispose函数会将share_ptr引用的对象释放内存。
virtual void _M_dispose()// nothrow
{
COSTA_DEBUG_SHAREDPTR;
_M_del(_M_ptr);
}
_M_del是在构造_M_pi时候就初始化好的内存回收函数, _M_ptr就是shared_ptr引用的对象指针。
下面是我自己实现的share_ptr
#include<iostream>
#include<vector>
/*
1.auto_ptr 所有权唯一,只能有一个对象使用
1.智能指针提前失效
2.带标志位的智能指针 所有权不唯一 释放权唯一
3.boost:: scope_ptr
*/
class Ref_Management
{
public:
static Ref_Management* getInstance()//引用计数类的接口,模拟__shared_count
{
return &rm;
}
private:
Ref_Management():cursize(0){}
Ref_Management(const Ref_Management&);
static Ref_Management rm;
public:
//添加一个指针指向一块内存
void addref(void* mptr)
{
if (mptr != NULL)
{
int index = find(mptr);
if (index < 0)
{
Node tmp(mptr, 1);
node[cursize++] = tmp;
//node[cursize].addr = mptr;
//node[cursize].ref = 1;
//cursize++;
}
else
{
node[index].ref++;
}
//std::vector<Node>::iterator fit = find(mptr);
//if (fit == vec.end())
//{
// Node node(mptr, 1);
// vec.push_back(node);
//}
//else
//{
// (*fit).ref++;
//}
}
}
//删除一个指针
void delref(void* mptr)
{
if (mptr != NULL)
{
int index = find(mptr);
if (index < 0)
{
throw std::exception("addr is not exsit!");
}
else
{
if (node[index].ref != 0)
{
node[index].ref--;
}
}
}
}
//查找指针所在的位置的引用个数
int getref(void* mptr)
{
int rt = -1;
if (mptr != NULL)
{
int index = find(mptr);
if (index >= 0)
{
rt = node[index].ref;
}
}
return rt;
}
private:
//查找指针指向的位置
int find(void* mptr)
{
int rt = -1;
for (int i = 0; i < cursize; i++)
{
if (node[i].addr == mptr)
{
rt = i;
break;
}
}
return rt;
/*std::vector<Node>::iterator it = vec.begin();
for (it; it != vec.end(); it++)
{
if ((*it).addr == mptr)
break;
}
return it;*/
}
//addr:存放的指针,ref:存放指针的位置的个数,引用计数
class Node
{
public:
Node(void* padd = NULL, int rf = 0) :addr(padd), ref(rf){}
public:
void* addr;
int ref;
};
Node node[10];//数组的整体大小,数组中存放的是指针和指针所在位置的个数
int cursize;//数组当前的大小
};
Ref_Management Ref_Management::rm;
template<typename T>
class Shared_Ptr
{
public:
//构造函数,调用AddRef();添加内存
Shared_Ptr(T* ptr = NULL) :mptr(ptr)
{
AddRef();
}
//拷贝构造函数 ,申请一个新的内存,引用计数为一,将数组的当前大小加一
Shared_Ptr(const Shared_Ptr<T>& rhs) :mptr(rhs.mptr)
{
AddRef();
}
//赋值运算符的重载函数,自赋值的的判断,调用~Share_ptr(),资源复制,调用AddRef();
Shared_Ptr<T>& operator=(const Shared_Ptr<T>& rhs)
{
if (this != &rhs)
{
this->~Shared_Ptr();
mptr = rhs.mptr;
AddRef();
}
return *this;
}
//析构函数,调用DelRef(),释放内存,并指向NULL;
~Shared_Ptr()
{
DelRef();
if (GetRef() == 0)
{
delete mptr;
}
mptr = NULL;
}
//->运算符重载函数
T* operator->()
{
return mptr;
}
// * 运算符重载函数
T& operator*()
{
return *mptr;
}
private:
//调用addref(),传入mptr,添加内存
void AddRef()
{
prm->addref(mptr);
}
//调用delref(),传入mptr,释放内存
void DelRef()
{
prm->delref(mptr);
}
//调用getref(),传入mptr,得到当前位置的引用计数
int GetRef()
{
return prm->getref(mptr);
}
T* mptr;
static Ref_Management* prm;//提供静态的接口,供本类可以调用prm对象的函数
};
template<typename T>
Ref_Management* Shared_Ptr<T>::prm = Ref_Management::getInstance();
class B;
class A
{
public:
A()
{
std::cout << "A()" << std::endl;
}
~A()
{
std::cout << "~A()" << std::endl;
}
public:
Shared_Ptr<B> spa;
};
class B
{
public:
B()
{
std::cout << "B()" << std::endl;
}
~B()
{
std::cout << "~B()" << std::endl;
}
public:
Shared_Ptr<A> spb;
};
/*
shared_ptr 相互引用
*/
/*
weak_ptr解决问题
*/
int main()
{
Shared_Ptr<A> pa(new A());
Shared_Ptr<B> pb(new B());
pa->spa = pb;
pb->spb = pa;
return 0;
}
关于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对象提供与内置类型相同的线程安全级别。多个线程可以同时“读取”(仅使用常量操作访问)共享_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引用的对象的值应该不变。
程序如下:
这个程序中我启了10个线程。每个线程调用10万次 CostaSwapSharedPtr2函数。 在CostaSwapSharePtr2函数中,对同一个share_ptr全局变量gp进行两次swap(写操作), 在函数返回之后检查gp的值是否被修改。如果gp值被修改,则证明多线程对同一个share_ptr执行写操作是不安全的。
程序运行的结果如下:
Thread quit
Thread error. *gp=1000
Thread quit
Thread error. *gp=1000
Thread quit
Thread error. *gp=1000
Thread quit
Thread error. *gp=1000
Thread quit
Thread error. *gp=1000
Thread quit
Thread error. *gp=1000
Thread error. *gp=1000
Thread quit
Thread quit
10个线程有9个出错。证明多线程对同一个share_ptr执行写操作是不安全的。我们在程序中,如果不运行CostaSwapSharedPtr2, 改成运行CostaSwapSharedPtr1呢? CostaSwapSharedPtr1和CostaSwapSharedPtr2的区别在于, 它不是直接对全局变量gp进行写操作,而是将gp拷贝出来一份再进行写操作。运行的结果如下:
Thread quit
Thread quit
Thread quit
Thread quit
Thread quit
Thread quit
Thread quit
Thread quit
Thread quit
跑了很多次都没有出错。说明共享引用计数的不同的shared_ptr执行swap是线程安全的。BOOST文档是可信的。
补充一个问题: 为什么shared_ptr可以作为STL标准容器的元素,而auto_ptr不可以 这个根据auto_ptr相信也可以找出答案了,以及auto_ptr为什么会被慢慢摒弃了;
这篇文章小结一下:
1 shared_ptr是一个非常实用的智能指针。
2 shared_ptr的实现机制是在拷贝构造时使用同一份引用计数。
3 对同一个shared_ptr的写操作不是线程安全的。 对使用同一份引用计数的不同shared_ptr是线程安全的。
线程安全测试取自:http://my.oschina.net/costaxu/blog/103119