C++中各种智能指针的实现及弊端(四)

C++中各种智能指针的实现及弊端(四)

一、std::shared_ptr
std::shared_ptr文档

int main()
{
// shared_ptr通过引用计数支持智能指针对象的拷贝
shared_ptr<Date> sp(new Date);
shared_ptr<Date> copy(sp);
cout << "ref count:" << sp.use_count() << endl;
cout << "ref count:" << copy.use_count() << endl;
return 0;
}

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。例如:

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指
    针了
    详细请参考:引用计数+浅拷贝=解决浅拷贝
// 模拟实现一份简答的SharedPtr,了解原理
#include <thread>
#include <mutex>
template <class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = nullptr)
: _ptr(ptr)
, _pRefCount(new int(1))
, _pMutex(new mutex)
{}
~SharedPtr() {Release();}
SharedPtr(const SharedPtr<T>& sp)
: _ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pMutex(sp._pMutex)
{
AddRefCount();
}
// sp1 = sp2
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
//if (this != &sp)
if (_ptr != sp._ptr)
{
// 释放管理的旧资源
Release();
// 共享管理新对象的资源,并增加引用计数
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pMutex = sp._pMutex;
AddRefCount();
}
return *this;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
int UseCount() {return *_pRefCount;}
T* Get() { return _ptr; }
void AddRefCount()
{
// 加锁或者使用加1的原子操作
_pMutex->lock();
++(*_pRefCount);
_pMutex->unlock();
}
private:
void Release()
{
bool deleteflag = false;
// 引用计数减1,如果减到0,则释放资源
_pMutex.lock();
if (--(*_pRefCount) == 0)
{
delete _ptr;
delete _pRefCount;
deleteflag = true;
}
_pMutex.unlock();
if(deleteflag == true)
delete _pMutex;
}
private:
int* _pRefCount; // 引用计数
T* _ptr; // 指向管理资源的指针
mutex* _pMutex; // 互斥锁
};
int main()
{
SharedPtr<int> sp1(new int(10));
SharedPtr<int> sp2(sp1);
*sp2 = 20;
cout << sp1.UseCount() << endl;
cout << sp2.UseCount() << endl;
SharedPtr<int> sp3(new int(10));
sp2 = sp3;
cout << sp1.UseCount() << endl;
cout << sp2.UseCount() << endl;
cout << sp3.UseCount() << endl;
sp1 = sp3;
cout << sp1.UseCount() << endl;
cout << sp2.UseCount() << endl;
cout << sp3.UseCount() << endl;
return 0;
}

上面的代码采用加锁的方式;主要是为了:防止计数_pRefCount出现错乱问题

二、std::shared_ptr的线程安全问题:

通过下面的程序我们来测试shared_ptr的线程安全问题。需要注意的是shared_ptr的线程安全分为两方面:

1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的。

2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。

// 1.演示引用计数线程安全问题,就把AddRefCount和SubRefCount中的锁去掉
// 2.演示可能不出现线程安全问题,因为线程安全问题是偶现性问题,main函数的n改大一些概率就变大了,就
容易出现了。
// 3.下面代码我们使用SharedPtr演示,是为了方便演示引用计数的线程安全问题,将代码中的SharedPtr换成
shared_ptr进行测试,可以验证库的shared_ptr,发现结论是一样的。
void SharePtrFunc(SharedPtr<Date>& sp, size_t n)
{
cout << sp.Get() << endl;
for (size_t i = 0; i < n; ++i)
{
// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
SharedPtr<Date> copy(sp);
// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但是最
终看到的结果,并一定是加了2n
copy->_year++;
copy->_month++;
copy->_day++;
}
}
int main()
{
SharedPtr<Date> p(new Date);
cout << p.Get() << endl;
const size_t n = 100;
thread t1(SharePtrFunc, p, n);
thread t2(SharePtrFunc, p, n);
t1.join();
t2.join();
cout << p->_year << endl;
cout << p->_month << endl;
cout << p->_day << endl;
return 0;
}

通过上面的代码验证了如果把AddRefCount和SubRefCount中的锁去掉,可能会造成线程安全问题;因为线程是抢占式执行的,那个线程先访问计数_pRefCount是不确定的,所以就可能会造成计数_pRefCount错乱,不是线程安全的。

但是上面的代码还是有问题的:存在循环引用问题;
解决方法:解决循环引用问题

发布了49 篇原创文章 · 获赞 15 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/wolfGuiDao/article/details/103797547