智能指针基本原理,简单实现,常见问题
基本概念
- 智能指针是一个模板;
- shared_ptr允许多个指针指向同一个对象,unique指针则独占指向的对象;
基本使用
-
shared_ptr<T> ptr; //默认初始化保存着一个空指针
-
shared_ptr<int> ptr = make_shared<int>(42);
-
拷贝与赋值,会有一个引用计数
引用计数增加的情况:
- 拷贝初始化:
shared_ptr<T>q(p);
- 参数传递及函数返回值:
void function(shared_ptr<T> ptr);
因为这也是一种拷贝
- 拷贝初始化:
基本实现
-
两个基本成员ptr与ref_count,即指针与引用计数,关于引用计数是一个数值还是一个稍复杂的类,要看库的具体实现,此处为了简便使用一个数值来统计引用计数;
ptr ref_count -
需实现函数:
- 显式初始化构造函数
- 拷贝构造函数
- 析构函数
#include<iostream>
#include<mutex>
#include<thread>
using namespace std;
template<class T>
class Shared_Ptr{
public:
Shared_Ptr(T* ptr = nullptr) // 默认构造函数
:_pPtr(ptr)
, _pRefCount(new int(1))
, _pMutex(new mutex)
{}
~Shared_Ptr() // 定制析构函数
{
Release();
}
Shared_Ptr(const Shared_Ptr<T>& sp) // 拷贝构造函数,增加引用计数
:_pPtr(sp._pPtr)
, _pRefCount(sp._pRefCount)
, _pMutex(sp._pMutex)
{
AddRefCount();
}
Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp) // 重载赋值运算符
{
//if (this != &sp)
if (_pPtr != sp._pPtr)
{
// 释放管理的旧资源
Release();
// 共享管理新对象的资源,并增加引用计数
_pPtr = sp._pPtr;
_pRefCount = sp._pRefCount;
_pMutex = sp._pMutex;
AddRefCount();
}
return *this;
}
T& operator*(){
return *_pPtr;
}
T* operator->(){
return _pPtr;
}
int UseCount() { return *_pRefCount; }
T* Get() { return _pPtr; }
void AddRefCount()
{
_pMutex->lock();
++(*_pRefCount);
_pMutex->unlock();
}
private:
void Release()
{
bool deleteflag = false;
_pMutex->lock();
if (--(*_pRefCount) == 0)
{
delete _pRefCount;
delete _pPtr;
deleteflag = true;
}
_pMutex->unlock();
if (deleteflag == true)
delete _pMutex;
}
private:
int *_pRefCount; // 计数器
T* _pPtr; // 指针成员
mutex* _pMutex;
};
线程安全问题
- 引用计数是多个智能指针对象共享,若智能指针处于不同的线程内,则线程并行操作有可能引起技术混乱或指针空悬问题;
- 计数混乱:为了解决计数混乱问题,可以加互斥锁在计数变量上,这样每次只会有一个线程执行变量的加减操作;即在实现中引用计数的操作是线程安全的。
- 指针空悬:对指针的操作不是线程安全的。如对于多线程中的两个智能指针a,b,若在某线程1中想要执行赋值操作a=b,分两步执行,1)先执行指针的复制,2)再执行引用计数的复制并加1,这两步操作不是原子的. 如果执行完第一步后,转到了线程2执行
b = (new ClassA)
,即对于智能指针b来说,他被赋予了新值,原计数减一,由于线程一中的增加引用计数操作还未来得及实施,所以现在引用计数变成了0,原指针被释放。如果此时再回到线程1,a的引用计数指针将指向新的b的引用计数,这里就产生了错误,并且a的指针空悬,产生安全问题。 - 具体图示可见link:https://blog.csdn.net/liang19890820/article/details/120465794
循环引用
可以假设一个双向链表Node的结构体,其中的next与prev都设置为智能指针,然后创建两个智能指针node1, ndoe2
指向两个新new的Node,那么这时候两者的引用计数都是1.此时再分别设置node1->next = node2; node2->next = node1
,两者的引用计数都变为2,若此时程序结束,则两node引用值都变为1,无法析构,。想要node1析构得先析构node2,反之亦然,这就导致了循环。
- 解决方法:shared_ptr改为weak_ptr,弱指针不会增加引用计数,就不会循环引用了
石中之火,即使无可燃烧之物,也要尽力发亮