共享指针shared_ptr是RALL机制的一个典型实现,主要功能是管理动态创建对象的销毁,从而帮助彻底消除内存泄漏和悬空指针(野指针)的问题,并且通过引用计数的方式实现了可以多个共享指针可以指向同一个对象。
由于最近经常看到这个问题,查阅了相关文献,自己来尝试手写一个简易的shared_ptr,有错误还望指出。
具体地,一个简易的shared_ptr应该包括以下几个部分:
1、构造函数与析构函数,动态创建及销毁对象。
2、重载拷贝构造函数,个人理解是为了使动态指针能够像普通指针一样进行值传递。
3、重载=赋值运算符,->运算符以及*运算符使其更像指针。
最主要部分还是要维护创建的指针以及引用计数。
首先看一下整体的框架
tmplate<class T>
class new_shared_ptr {
pubilc:
new_shared_ptr();
new_shared_ptr(const new_shared_ptr& p);
~new_shared_ptr();
new_shared_ptr& operator=(const new_shared_ptr<T>& obj);
int operator->();
int operator*();
private:
T* ptr;
int* ref_count;
mutex *m_pMutex;
}
首先实现一下构造函数与拷贝构造函数,主要是对指针、引用计数以及锁进行初始化,注意引用计数改变时需要加锁,保证线程安全
//构造函数
new_shared_ptr(): ptr(new T), ref_count(new int(1)), mutex(new mutex) {}
//拷贝构造函数
new_shared_ptr(const new_shared_ptr& p) {
//先对当前对象进行初始化
ptr = p.ptr;
ref_count = p.ref_count;
mutex = p.mutex;
//判断拷贝进来的指针是否指向空,不指向空表明当前地址多了一个指向,引用计数加一
if (p != nullptr) {
m_pMutex->lock();
(*ref_count)++;
m_pMutex->unlock();
}
}
重载=赋值运算符,->运算符以及*运算符。
new_shared_ptr& operator=(const new_shared_ptr<T>& obj) {
//检查自赋值
if (obj.ptr == ptr) return *this;
//防止空指针指向别处
if (ptr) {
//自身指向即将改变,判断原先指向的地址是否销毁
m_pMutex->lock();
(*ref_count)--;
m_pMutex->unlock();
if (ref_count == 0) {
delete ptr;
delete ref_count;
delete m_pMutex;
}
ptr = obj.ptr;
ref_count = obj.ref_count;
m_pMutex = obj.m_pMutex;
m_pMutex->lock();
(*ref_count)++;
m_pMutex->unlock();
}
return *this;
}
T* operator->() return ptr;
T& operator*() return *ptr;
最后是析构函数,同样是判断引用计数的值,如果不为零则只减一,为0才进行销毁
~new_shared_ptr() {
if (!ptr || (*ref_count == 1)) {
delete ptr;
delete m_pMutex;
delete ref_count;
} else {
m_pMutex->lock();
(*ref_count)++;
m_pMutex->unlock();
}
}
可以再写一个获取当前引用计数的接口
int get_count() {
return *ref_count;
}
至此一个简单的线程安全的共享指针就写完了,当然实际上的共享指针还应该包括移动构造函数等,这里就不一一列出,再说一个我遇到的问题:
为什么引用计数要声明为指针?
主要是为了保证所有指向该内存的指针共享同一份数据,每次指针拷贝的时候,实际上是拷贝的指针,该指针同样指向的是相同的地址,所以实现了一个同步的功能。为了防止出现两个指针同时对某一块内存操作,每次对引用计数的操作还需要加锁来保证线程安全。