目录
1.RAII
1.1什么是RAII
1.2RAII的原理
1.3RAII的好处
2.auto_ptr
3.unique_ptr
4.1线程安全问题
4.2循环引用
4.3weak_ptr
RAII(Resource Acquisition Is Initialization)是由c++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化,他是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术
资源的使用一般经历三个步骤a.获取资源 b.使用资源 c.销毁资源,但是资源的销毁往往是程序员经常忘记的一个环节,所以程序界就想如何在程序员中让资源自动销毁呢?c++之父给出了解决问题的方案:RAII(实现资源的自动销毁)它充分的利用了C++语言局部对象自动销毁的特性来控制资源的生命周期,实际上把管理一份资源的责任托管给了一个对象
给一个简单的例子来看下局部对象的自动销毁的特性:
#include
#include
#include
using namespace std;
//#include "SmartPtr.h"
// 使用RAII思想设计的SmartPtr类
template
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
delete _ptr;
}
private:
T* _ptr;
};
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void Func()
{
SmartPtr sp1(new int);
SmartPtr sp2(new int);
cout << div() << endl;
}
int main()
{
try {
Func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
但是这样类还不能封装好智能指针,因为他没有和指针一样的行为
要支持*和->
template
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
delete _ptr;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
};
struct Date
{
int _year=1;
int _month=1;
int _day=1;
};
int main()
{
SmartPtr sp1(new int);
*sp1 = 10;
cout << *sp1 << endl;
SmartPtr sparray(new Date);
// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
sparray->_year = 2018;
sparray->_month = 1;
sparray->_day = 1;
return 0;
}
总结一下智能指针的原理:
1. RAII特性
2. 重载operator*和opertaor->,具有像指针一样的行为
首先是最遭受骂名的指针,查阅文档发现这个指针甚至被明确表明不适用于C++11,因为它存在线程安全的问题,这个指针最大的特点就是管理权悬空
实现如下:
namespace wrt
{
template
class auto_ptr //管理权转移
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{
cout << " auto_ptr(T * ptr)" << endl;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
auto_ptr(auto_ptr& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
~auto_ptr()
{
if (_ptr)
{
cout << "~auto_ptr()" << endl;
delete _ptr;
}
}
private:
T* _ptr;
};
void test_auto_ptr()
{
auto_ptr ap1(new int(1));
auto_ptr ap2(ap1);
//*ap1 = 10; //此时ap1悬空导致不能访问
*ap2 = 20;
cout << *ap2 << endl;
}
}
由于使用ap1拷贝构造ap2,导致
auto_ptr(auto_ptr& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
此时的原指针即ap1被悬空(置位nullptr)所以.....他就废掉了不能进行访问
注意下头文件
他是一个简单粗暴的用类封装的指针,行为和指针一样的,但是他不支持拷贝——防拷贝
template
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr) {}
T& operator*()
{
return *_ptr;
}
T& operator->()
{
return _ptr;
}
//简单粗暴,防止拷贝
//C++11
unique_ptr(const unique_ptr& uq) = delete;
unique_ptr& operator=(const unique_ptr& uq) = delete;
//C++98
//private:
// unique_ptr(const unique_ptr& uq);
~unique_ptr()
{
if (_ptr)
std::cout << "delete:" << _ptr << std::endl;
delete _ptr;
}
private:
T* _ptr;
};
void test_unique_ptr()
{
unique_ptr uq(new int(1));
}
他和前面unique_ptr的区别就是他支持拷贝,并且提供引用计数
首先明确一个资源可能是被多个指针指向的,所以对于资源来说,要求支持引用计数——指向他的指针个数
引用计数本质就是一个int*指针
尤其要注意shared_ptr的拷贝赋值函数,正常的指针就是支持拷贝赋值的
相同资源之间的赋值就没有实现的必要,现实场景中一般没有这个情况
shared_ptr& operator=(const shared_ptr& sp)
{
//相同的资源之间相互赋值不建议写
//if(&sp != this) //因为可能本身就不同的对象但是管理着相同的资源,不用对象去判断,用资源判断
if (sp._ptr != _ptr) //说明不是一个资源
{
Rease();
_ptr = sp._ptr;
_pcount = sp._pcount;
Addcount();
}
return *this;
}
void Rease()
{
if ((--(*_pcount)) == 0) //此时要进行资源释放
{
if (_ptr)
{
cout << "delete:" <<_ptr<< endl;
delete _ptr;
}
delete _pcount; //引用计数也应该释放
}
}
首先看一个雏形
template
class shared_ptr
{
public:
shared_ptr( T*ptr=nullptr, int* pcount=new int(1))
:_ptr(ptr),_pcount(pcount)
{}
//因为后面多次涉及析构,所以封装成函数
void Rease()
{
if ((--(*_pcount)) == 0)
{
if (_ptr)
{
cout << "delete:" <<_ptr<< endl;
delete _ptr;
}
delete _pcount;
}
}
~shared_ptr()
{
Rease();
}
void Addcount()
{
(*_pcount)++;
}
//拷贝构造
shared_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
{
Addcount();
}
shared_ptr& operator=(const shared_ptr& sp)
{
//相同的资源之间相互赋值不建议写
//if(&sp != this) //因为可能本身就不同的对象但是管理着相同的资源,不用对象去判断,用资源判断
if (sp._ptr != _ptr) //说明不是一个内存
{
Rease();
_ptr = sp._ptr;
_pcount = sp._pcount;
Addcount();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get_ptr() const
{
return _ptr;
}
int use_count() const
{
return *_pcount;
}
private:
T* _ptr;
int* _pcount;
};
显然这个是有线程安全问题
void Rease()
{
if ((--(*_pcount)) == 0)
{
if (_ptr)
{
cout << "delete:" <<_ptr<< endl;
delete _ptr;
}
delete _pcount;
}
}
就这个释放函数而言
假设t1和t2都进入了第一个if ,t1首先进入判断,但是没有删除就被切走,此时t2判断第二个if依然满足,进入释放_ptr,等到t1回来重复释放了_ptr
想控制引用计数的方法:
1.加锁 2.用automic,但是这样做非常的复杂
加锁浅浅实现一下
template
class shared_ptr
{
public:
shared_ptr( T*ptr=nullptr, int* pcount=new int(1))
:_ptr(ptr),_pcount(pcount),_pmtx(new mutex)
{}
//定时删除器 用function进行包装
template
shared_ptr(T* ptr , D del)
:_ptr(ptr), _pcount( new int(1)), _pmtx(new mutex)
{}
//因为后面多次涉及析构,所以封装成函数
void Rease()
{
_pmtx->lock();
if ((--(*_pcount)) == 0)
{
if (_ptr)
{
cout << "delete:" <<_ptr<< endl;
delete _ptr;
}
delete _pcount;
//如何解决 delete _pmtx
}
_pmtx->unlock();
}
~shared_ptr()
{
Rease();
}
void Addcount()
{
_pmtx->lock();
(*_pcount)++;
_pmtx->unlock();
}
//拷贝构造
shared_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
,_pmtx(sp._pmtx)
{
Addcount();
}
shared_ptr& operator=(const shared_ptr& sp)
{
//相同的资源之间相互赋值不建议写
//if(&sp != this) //因为可能本身就不同的对象但是管理着相同的资源,不用对象去判断,用资源判断
if (sp._ptr != _ptr) //说明不是一个内存
{
{
Rease();
}
_ptr = sp._ptr;
_pcount = sp._pcount;
_pmtx = sp._pmtx;
Addcount();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get_ptr() const
{
return _ptr;
}
int use_count() const
{
return *_pcount;
}
private:
T* _ptr;
int* _pcount;
std::mutex* _pmtx;
};
加锁还是有一个问题 当计数减为0时对_pcount释放,还要释放锁,但是还没解锁呐!!!
要解决这个问题就要加一个bool 标志位
void Rease()
{
_pmtx->lock();
bool daleteflage = false; //默认是没有删除的
if ((--(*_pcount)) == 0)
{
if (_ptr)
{
cout << "delete:" <<_ptr<< endl;
//delete _ptr;
}
delete _pcount;
//如何解决 delete _pmtxx->用一个bool标志位
daleteflage = true;
}
_pmtx->unlock();
if (daleteflage)
delete _pmtx;
}
现在看起来引用计数肯定是没问题了
使用两个线程测试一下吧
class Date
{
public:
Date() {}
Date(int year, int month, int day)
:_year(year), _month(month), _day(day) {}
~Date()
{}
Date(const Date& d)
{
_month = d._month;
_day = d._day;
_year = d._year;
}
public:
int _year = 0;
int _month = 0;
int _day = 0;
};
void SharePtrFunc(wrt::shared_ptr& sp, size_t n, mutex& mtx)
{
for (size_t i = 0; i < n; ++i)
{
// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
wrt::shared_ptr copy(sp);
// 智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n
//次,但是最终看到的结果,并一定是加了2n
{
mtx.lock();
copy->_year++;
copy->_month++;
copy->_day++;
mtx.unlock();
}
}
}
void test_shared_safe()
{
wrt::shared_ptr p(new Date);
cout << p.get_ptr() << endl;
const size_t n = 100000;
mutex mtx;
thread t1(shareptrfunc,ref(p), n, ref(mtx));
thread t2(shareptrfunc, ref(p), n, ref(mtx));
t1.join();
t2.join();
cout << p.use_count() << endl;
cout << p->_year << endl;
cout << p->_month << endl;
cout << p->_day << endl;
}
智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但是最终看到的结果,并一定是加了2n,并且每次运行的结果都不一样
显然就是出现了线程安全的问题,但是刚才引用计数是没问题的啊
shared_ptr本身是线程安全的,因为引用计数是加锁保护的,但是shared_ptr管理的对象是不是安全的?不是 (根据上个例子可知)
把命名空间改成std发现结果还是一样的不对!
说明他管理的对象就是不安全的
在特定的场景下,ListNode中可能会在互相指向过程中抛异常,导致无法释放
struct ListNode
{
shared_ptr _next; //要注意智能指针必须有默认构造函数(库里也有)
shared_ptr _prev;
int _val=10;
~ListNode()
{
cout << "~ListNode" << endl;
}
};
void test_shared_cycle()
{
wrt::shared_ptr n1 = new ListNode;//其实这样写是不对的,相当于ListNode*指针隐式型转换,库里面在带参构造的函数前面加上了explicit(不允许隐式类型转换)
wrt::shared_ptr n2 = new ListNode;
}
库里面不支持这种转换
换种写法:
struct ListNode
{
shared_ptr _next; //要注意智能指针必须有默认构造函数(库里也有)
shared_ptr _prev;
int _val=10;
~ListNode()
{
cout << "~ListNode" << endl;
}
};
void test_shared_cycle()
{
wrt::shared_ptr n1(new ListNode);
wrt::shared_ptr n2(new ListNode); //此时有智能指针管就不用考虑释放
}
现在考虑指向的问题
n1->_next = n2;
n2->_prev = n1; //智能指针不能给给shared_ptr
只屏蔽一个指向会正常释放但是两个指向就什么都不会打印 是因为循环引用,本身shared_ptr设计导致极端场景的坑,分析一下场景(画图)
假设只让n1->next=n2;
释放_next之后_prev的引用计数减为1,此时释放_prev就可以正常释放资源
同理n2->_prev=n1;
两个指向就什么都不会打印,是因为如果要释放next的资源就要释放管着这个资源的prev,同理要释放prev就要释放管着prev资源的next 导致出现闭环——循环引用
循环引用导致内存泄漏,但是我还想保持指向只是不想让next和prev参与管理
为了解决循环引用的问题,引入waek_ptr指针
weak_ptr的特点是:
//1.不符合RAII,不是常规智能指针
//2.支持像指针一样,本质是专门设计出来解决shared_ptr循环应用的问题,不可以单独使用
//3.weak_ptr本身是有引用计数的,但是他指向资源但不参与管理
template
class weak_ptr
{
public:
weak_ptr() :_ptr(nullptr),_pcount(new int(1))
{}
weak_ptr(const shared_ptr& sp) :_ptr(sp.get_ptr()),_pcount(new int(sp.use_count())) {}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get_ptr() const
{
return _ptr;
}
int use_count()
{
return *_pcount;
}
private:
T* _ptr;
int* _pcount;
};
4.4定制删除器
shared_ptr释放特定类型的本质就是定制删除器,删除器就是可调用对象,包括:函数指针,lambda函数,仿函数
//定制删除器
template
struct DeleteDate
{
void operator()(T* _ptr)
{
cout << "void operator()(T * _ptr)" << endl;
delete[]_ptr;
}
T* _ptr;
};
void test_shared_deletor()
{
wrt::shared_ptr sp1(new Date[10],DeleteDate());
wrt::shared_ptr sp2(new Date[10], [](Date* ptr) {
cout << "lambda delete" << endl;
delete[]ptr; });
wrt::shared_ptr spF3(fopen("Test.cpp","r"), [](FILE* ptr) {
cout << "fclose()" << endl;
fclose(ptr); });
}
把以上所讲的所有应用到shared_ptr的最终代码
namespace wrt
{
template
class shared_ptr
{
public:
shared_ptr( T*ptr=nullptr, int* pcount=new int(1))
:_ptr(ptr),_pcount(pcount),_pmtx(new mutex)
{}
//定时删除器 用function进行包装
template
shared_ptr(T* ptr , D del)
:_ptr(ptr), _pcount( new int(1)), _pmtx(new mutex),_del(del)
{}
//因为后面多次涉及析构,所以封装成函数
void Rease()
{
_pmtx->lock();
bool daleteflage = false;
if ((--(*_pcount)) == 0)
{
if (_ptr)
{
cout << "delete:" <<_ptr<< endl;
//delete _ptr;
_del(_ptr); //用包装器释放
}
delete _pcount;
//如何解决 delete _pmtxx->用一个bool标志位
daleteflage = true;
}
_pmtx->unlock();
if (daleteflage)
delete _pmtx;
}
~shared_ptr()
{
Rease();
}
void Addcount()
{
_pmtx->lock();
(*_pcount)++;
_pmtx->unlock();
}
//拷贝构造
shared_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
,_pmtx(sp._pmtx)
{
Addcount();
}
shared_ptr& operator=(const shared_ptr& sp)
{
//相同的资源之间相互赋值不建议写
//if(&sp != this) //因为可能本身就不同的对象但是管理着相同的资源,不用对象去判断,用资源判断
if (sp._ptr != _ptr) //说明不是一个内存
{
{
Rease();
}
_ptr = sp._ptr;
_pcount = sp._pcount;
_pmtx = sp._pmtx;
Addcount();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get_ptr() const
{
return _ptr;
}
int use_count() const
{
return *_pcount;
}
private:
T* _ptr;
int* _pcount;
std::mutex* _pmtx;
function _del = [](T* ptr) {
cout << "lambda" << ptr << endl;
delete ptr;
};//包装器
};
class Date
{
public:
Date() {}
Date(int year, int month, int day)
:_year(year), _month(month), _day(day) {}
~Date()
{}
Date(const Date& d)
{
_month = d._month;
_day = d._day;
_year = d._year;
}
public:
int _year = 0;
int _month = 0;
int _day = 0;
};
void SharePtrFunc(wrt::shared_ptr& sp, size_t n, mutex& mtx)
{
for (size_t i = 0; i < n; ++i)
{
// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
wrt::shared_ptr copy(sp);
// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n
//次,但是最终看到的结果,并一定是加了2n
{
mtx.lock();
copy->_year++;
copy->_month++;
copy->_day++;
mtx.unlock();
}
}
}
template
class weak_ptr
{
public:
weak_ptr() :_ptr(nullptr),_pcount(new int(1))
{}
weak_ptr(const shared_ptr& sp) :_ptr(sp.get_ptr()),_pcount(new int(sp.use_count())) {}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get_ptr() const
{
return _ptr;
}
int use_count()
{
return *_pcount;
}
private:
T* _ptr;
int* _pcount;
};
//void test_shared_safe()
//{
// wrt::shared_ptr p(new Date);
// cout << p.get_ptr() << endl;
// const size_t n = 100000;
// mutex mtx;
// thread t1(shareptrfunc,ref(p), n, ref(mtx));
// thread t2(shareptrfunc, ref(p), n, ref(mtx));
// t1.join();
// t2.join();
// cout << p.use_count() << endl;
// cout << p->_year << endl;
// cout << p->_month << endl;
// cout << p->_day << endl;
//}
void test_shared_ptr()
{
/*shared_ptr sq1(new int(1));
shared_ptr sq2(sq1);*/
shared_ptr day1(new Date(2023, 7, 4));
shared_ptr day2(day1);
shared_ptr day3(new Date(2022,4,4));
day3 = day1;
}
struct ListNode
{/*
ListNode* _next;
ListNode* _prev;*/
shared_ptr _next; //但是这样会报错说没有默认构造函数,是因为只能指针没有默认构造函数
shared_ptr _prev;
//wrt::weak_ptr _next;
//wrt::weak_ptr _prev;
int _val=10;
~ListNode()
{
cout << "~ListNode" << endl;
}
};
void test_shared_cycle()
{
//ListNode* n1 = new ListNode;
//ListNode* n2 = new ListNode;
//n1->_next = n2; //但是可能这里会抛异常,这样就无法正常释放,所以考虑使用只能指针
//n2->_prev = n1;
//delete n1;
//delete n2;
//wrt::shared_ptr n1 = new ListNode;//其实这样写是不对的,相当于ListNode*指针隐式型转换,库里面在带参构造的函数前面加上了explicit
//wrt::shared_ptr n2 = new ListNode;
//wrt::shared_ptr n1(new ListNode);
// wrt::shared_ptr n2(new ListNode); //此时有智能指针管就不用考虑释放
//现在考虑指向的问题
//n1->_next = n2;
//n2->_prev = n1; //智能指针不能给给shared_ptr
//只屏蔽一个指向会正常释放但是两个指向就什么都不会打印 是因为循环引用,本身shared_ptr设计导致极端场景的坑,分析一下场景(画图)
//循环引用导致内存泄漏,但是我还想保持指向只是不想让next和prev参与管理
//为了解决循环引用的问题,引入waek_ptr指针
//他的特点是:
//1.不符合RAII,不是常规智能指针
//2.支持像指针一样,本质是专门设计出来解决shared_ptr循环应用的问题
//3.weak_ptr本身是有引用计数的,但是他指向资源但不参与管理
}
//定制删除器
template
struct DeleteDate
{
void operator()(T* _ptr)
{
cout << "void operator()(T * _ptr)" << endl;
delete[]_ptr;
}
T* _ptr;
};
void test_shared_deletor()
{
wrt::shared_ptr sp1(new Date[10],DeleteDate());
wrt::shared_ptr sp2(new Date[10], [](Date* ptr) {
cout << "lambda delete" << endl;
delete[]ptr; });
wrt::shared_ptr spF3(fopen("Test.cpp","r"), [](FILE* ptr) {
cout << "fclose()" << endl;
fclose(ptr); });
}
}