1.为什么需要智能指针?
2. 内存泄漏
3.智能指针的使用及原理
4.C++11和boost中智能指针的关系
5.RAII扩展学习
1. 为什么需要智能指针?
下面我们先分析一下下面这段程序有没有什么
内存方面
的问题?提示一下:注意分析
MergeSort
函数中的问题。
#include <vector>
void _MergeSort(int* a, int left, int right, int* tmp) {
if (left >= right) return;
int mid = left + ((right - left) >> 1);
// [left, mid]
// [mid+1, right]
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int index = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
tmp[index++] = a[begin1++];
else
tmp[index++] = a[begin2++];
}
while (begin1 <= end1)
tmp[index++] = a[begin1++];
while (begin2 <= end2)
tmp[index++] = a[begin2++];
memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}
void MergeSort(int* a, int n) {
int* tmp = (int*)malloc(sizeof(int) * n);
_MergeSort(a, 0, n - 1, tmp);
// 这里假设处理了一些其他逻辑
vector<int> v(1000000000, 10);
// ...
// free(tmp);
}
int main()
{
int a[5] = { 4, 5, 2, 3, 1 };
MergeSort(a, 5);
return 0;
}
问题分析:上面的问题分析出来我们发现有以下两个问题?
1. malloc出来的空间,没有进行释放,存在内存泄漏的问题。
2. 异常安全问题。如果在malloc和free之间如果存在抛异常,那么还是有内存泄漏。这种问题就叫异常安全。
2. 内存泄漏
2.1 什么是内存泄漏,内存泄漏的危害
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不 是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的制,因而 造成了内存的浪费。 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}
2.2
内存泄漏分类(了解)
C/C++
程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏
(Heap leak)
堆内存指的是程序执行中依据须要分配通过
malloc / calloc / realloc / new
等从堆中分配的一块内存,用完后必须通过调用相应的 free
或者
delete
删掉。假设程序的设计错误导致这部分内存没有被释放,那 么以后这部分空间将无法再被使用,就会产生Heap Leak
。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定
2.3
如何检测内存泄漏(了解)
其他工具:内存泄漏工具比较
2.4如何避免内存泄漏
1.
工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。
ps
:这个理想状 态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保 证。
2.
采用
RAII
思想或者智能指针来管理资源。
3.
有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4.
出问题了使用内存泄漏工具检测。
ps
:不过很多工具都不够靠谱,或者收费昂贵。
总结一下
:
内存泄漏非常常见,解决方案分为两种:
1
、事前预防型。如智能指针等。
2
、事后查错型。如泄漏检测工具。
3.智能指针的使用及原理
3.1 RAII
RAII
(
Resource Acquisition Is Initialization
)是一种
利用对象生命周期来控制程序资源
(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源
,接着控制对资源的访问使之在对象的生命周期内始终保持有效,
最后在对象析构的
时候释放资源
。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
不需要显式地释放资源。 采用这种方式,对象所需的资源在其生命期内始终保持有效。
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{
}
~SmartPtr()
{
delete _ptr;
_ptr = nullptr;
}
private:
T* _ptr;
};
int main()
{
SmartPtr<int> sp(new int);
}
总结一下智能指针的原理:
1. RAII
特性
2.
重载
operator*
和
opertaor->
,具有像指针一样的行为。
C++98引入了一个智能指针auto_ptr,代码如下。
namespace MyPtr
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{
}
auto_ptr(auto_ptr<T>& sp) //管理权转移
{
_ptr = sp._ptr;
sp._ptr = nullptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~auto_ptr()
{
if (_ptr)
{
cout << "delete" << &_ptr << endl;
delete _ptr;
}
}
private:
T* _ptr;
};
}
int main()
{
MyPtr::auto_ptr<int> sp1(new int);
MyPtr::auto_ptr<int> sp2(sp1);
cout << *sp1 << endl;
}
这里可以看出auto_ptr其实是一个失败的设计,好多公司都禁止使用这个智能指针。他把之前拷贝对象的指针悬空了。这个指针失效了。
unique_ptr
C++11
中开始提供更靠谱的
unique_ptr
int main()
{
unique_ptr<Date> up(new Date);
// unique_ptr的设计思路非常的粗暴-防拷贝,也就是不让拷贝和赋值。
unique_ptr<Date> copy(ap);
return 0; }
// 模拟实现一份简答的UniquePtr,了解原理
template<class T>
class UniquePtr
{
public:
UniquePtr(T * ptr = nullptr)
: _ptr(ptr)
{}
~UniquePtr()
{
if(_ptr)
delete _ptr;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:
// C++98防拷贝的方式:只声明不实现+声明成私有
UniquePtr(UniquePtr<T> const &);
UniquePtr & operator=(UniquePtr<T> const &);
// C++11防拷贝的方式:delete
UniquePtr(UniquePtr<T> const &) = delete;
UniquePtr & operator=(UniquePtr<T> const &) = delete;
private:
T * _ptr;
};
shared_ptr
C++11中开始提供更靠谱的并且支持拷贝的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
,就说明除了自己还有其他对象在使用该份资源,
不能释放该资源
,否则其他对象就成野指针了
那么怎么解决计数的问题,有人就想出一个在静态区开辟一个计数,因为静态区的数据是所有对象共有的。代码如下。
namespace MyPtr
{
template<class T>
class share_ptr
{
public:
share_ptr(T* ptr)
:_ptr(ptr)
{
_pCount = 1;
}
share_ptr(const share_ptr<T>& sp)
:_ptr(sp._ptr)
{
++_pCount;
}
share_ptr<T>& operator=(const share_ptr<T>& sp)
{
_ptr = sp._ptr;
++_pCount;
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~share_ptr()
{
if (--_pCount == 0 && _ptr)
{
cout << "delete:" << &_ptr << endl;
delete _ptr;
}
}
private:
T* _ptr;
static int _pCount;
};
template<class T>
int share_ptr<T>::_pCount = 0;
}
int main()
{
MyPtr::share_ptr<int> sp1(new int);
MyPtr::share_ptr<int> sp2(sp1);
MyPtr::share_ptr<int> sp3(sp1);
//上面3个指针管理同一块资源,引用计数为3
MyPtr::share_ptr<int> sp4(new int);
//下面3个指针重新申请是把引用计数修改为1,明显错误
}
这说明在静态区开辟引用计数行不通,那么只有在堆上开辟空间。shared_ptr的线程安全问题
void SharePtrFunc(std::shared_ptr<Date>& sp, size_t n)
{
//cout << sp.Get() << endl;
for (size_t i = 0; i < n; ++i)
{
// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
std::shared_ptr<Date> copy(sp);
// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但
//是最终看到的结果,并一定是加了2n
copy->_year++;
copy->_month++;
copy->_day++;
}
}
int main()
{
std::shared_ptr<Date> p(new Date);
//cout << p.Get() << endl;
const size_t n = 10000;
thread t1(SharePtrFunc, std::ref(p), n);
thread t2(SharePtrFunc, std::ref(p), n);
t1.join();
t2.join();
cout << p->_year << endl;
cout << p->_month << endl;
cout << p->_day << endl;
cout << p.use_count() << endl;
return 0;
}
库里面的是一定保证线程安全的
可以看出线程安全的引用计数一定是1。
#include<memory>
#include<thread>
#include<mutex>
class Date
{
public:
int _year = 0;
int _month = 0;
int _day = 0;
};
namespace MyPtr
{
template<class T>
class share_ptr
{
public:
share_ptr(T* ptr)
:_ptr(ptr)
,_pCount(new int(1))
{
}
share_ptr(const share_ptr<T>& sp)
:_ptr(sp._ptr)
,_pCount(sp._pCount)
{
++(*_pCount);
}
share_ptr<T>& operator=(const share_ptr<T>& sp)
{
//if(this != &sp)
if (_ptr != sp._ptr) //要用原始指针判断,用类判断自己给自己赋值有问题
{
//if (--(*_pCount) == 0 && _ptr) //要释放以前的资源
//{
// cout << "delete:" << &_ptr <<endl;
// delete _ptr;
// delete _pCount;
//}
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
//++(*_pCount);
AddRef();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
void Release()
{
if (--(*_pCount) == 0 && _ptr) //要释放以前的资源
{
delete _ptr;
delete _pCount;
}
}
int use_count()
{
return *_pCount;
}
void AddRef()
{
++(*_pCount);
}
~share_ptr()
{
if (--(*_pCount) == 0 && _ptr)
{
cout << "delete:" << &_ptr << endl;
delete _ptr;
}
}
private:
T* _ptr;
int *_pCount;
};
}
//share_ptr是线程安全的,指在share_ptr内部引用计数是线程安全的,在外面线程安全是需要程序员自己手动保证
//线程安全的,所以在实现share_ptr是需要保证线程安全
void SharePtrFunc(MyPtr::share_ptr<Date>& sp, size_t n)
{
//cout << sp.Get() << endl;
for (size_t i = 0; i < n; ++i)
{
// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
MyPtr::share_ptr<Date> copy(sp);
// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但
//是最终看到的结果,并一定是加了2n
copy->_year++;
copy->_month++;
copy->_day++;
}
}
int main()
{
MyPtr::share_ptr<Date> p(new Date);
//cout << p.Get() << endl;
const size_t n = 10000;
thread t1(SharePtrFunc, std::ref(p), n);
thread t2(SharePtrFunc, std::ref(p), n);
t1.join();
t2.join();
cout << p->_year << endl;
cout << p->_month << endl;
cout << p->_day << endl;
cout << p.use_count() << endl;
return 0;
}
可以看出引用计数不是线程安全的。所以我们需要给引用计数上锁,但是同样需要保证同一块资源看到同一把锁。
#include<memory>
#include<thread>
#include<mutex>
class Date
{
public:
int _year = 0;
int _month = 0;
int _day = 0;
};
namespace MyPtr
{
template<class T>
class share_ptr
{
public:
share_ptr(T* ptr)
:_ptr(ptr)
,_pCount(new int(1))
,_mtx(new mutex)
{
}
share_ptr(const share_ptr<T>& sp)
:_ptr(sp._ptr)
,_pCount(sp._pCount)
{
++(*_pCount);
}
share_ptr<T>& operator=(const share_ptr<T>& sp)
{
//if(this != &sp)
if (_ptr != sp._ptr) //要用原始指针判断,用类判断自己给自己赋值有问题
{
//if (--(*_pCount) == 0 && _ptr) //要释放以前的资源
//{
// cout << "delete:" << &_ptr <<endl;
// delete _ptr;
// delete _pCount;
//}
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
//++(*_pCount);
AddRef();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
void Release()
{
_mtx->lock();
if (--(*_pCount) == 0 && _ptr) //要释放以前的资源
{
delete _ptr;
delete _pCount;
}
_mtx->unlock();
}
int use_count()
{
return *_pCount;
}
void AddRef()
{
_mtx->lock();
++(*_pCount);
_mtx->unlock();
}
~share_ptr()
{
if (--(*_pCount) == 0 && _ptr)
{
cout << "delete:" << &_ptr << endl;
delete _ptr;
}
}
private:
T* _ptr;
int *_pCount;
mutex* _mtx;
};
}
//share_ptr是线程安全的,指在share_ptr内部引用计数是线程安全的,在外面线程安全是需要程序员自己手动保证
//线程安全的,所以在实现share_ptr是需要保证线程安全
void SharePtrFunc(MyPtr::share_ptr<Date>& sp, size_t n)
{
//cout << sp.Get() << endl;
for (size_t i = 0; i < n; ++i)
{
// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
MyPtr::share_ptr<Date> copy(sp);
// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但
//是最终看到的结果,并一定是加了2n
copy->_year++;
copy->_month++;
copy->_day++;
}
}
int main()
{
MyPtr::share_ptr<Date> p(new Date);
//cout << p.Get() << endl;
const size_t n = 10000;
thread t1(SharePtrFunc, std::ref(p), n);
thread t2(SharePtrFunc, std::ref(p), n);
t1.join();
t2.join();
cout << p->_year << endl;
cout << p->_month << endl;
cout << p->_day << endl;
cout << p.use_count() << endl;
return 0;
}
#include<memory>
#include<thread>
#include<mutex>
class Date
{
public:
int _year = 0;
int _month = 0;
int _day = 0;
};
namespace MyPtr
{
template<class T>
class share_ptr
{
public:
share_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_pCount(new int(1))
,_mtx(new mutex)
{
}
share_ptr(const share_ptr<T>& sp)
:_ptr(sp._ptr)
,_pCount(sp._pCount)
,_mtx(sp._mtx)
{
//++(*_pCount);
AddRef();
}
share_ptr<T>& operator=(const share_ptr<T>& sp)
{
//if(this != &sp)
if (_ptr != sp._ptr) //要用原始指针判断,用类判断自己给自己赋值有问题
{
//if (--(*_pCount) == 0 && _ptr) //要释放以前的资源
//{
// cout << "delete:" << &_ptr <<endl;
// delete _ptr;
// delete _pCount;
//}
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
_mtx = sp._mtx;
//++(*_pCount);
AddRef();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
void Release()
{
_mtx->lock();
if (--(*_pCount) == 0) //要释放以前的资源
{
cout << "delete:" << &_ptr << endl;
delete _ptr;
delete _pCount;
}
_mtx->unlock();
}
int use_count()
{
return *_pCount;
}
void AddRef()
{
_mtx->lock();
++(*_pCount);
_mtx->unlock();
}
~share_ptr()
{
/*if (--(*_pCount) == 0 && _ptr)
{
cout << "delete:" << &_ptr << endl;
delete _ptr;
}*/
Release();
}
private:
T* _ptr;
int *_pCount;
mutex* _mtx;
};
}
//share_ptr是线程安全的,指在share_ptr内部引用计数是线程安全的,在外面线程安全是需要程序员自己手动保证
//线程安全的,所以在实现share_ptr是需要保证线程安全
void SharePtrFunc(MyPtr::share_ptr<Date>& sp, size_t n)
{
//cout << sp.Get() << endl;
for (size_t i = 0; i < n; ++i)
{
// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
MyPtr::share_ptr<Date> copy(sp);
// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但
//是最终看到的结果,并一定是加了2n
copy->_year++;
copy->_month++;
copy->_day++;
}
}
int main()
{
MyPtr::share_ptr<Date> p(new Date);
//cout << p.Get() << endl;
const size_t n = 10000;
thread t1(SharePtrFunc, std::ref(p), n);
thread t2(SharePtrFunc, std::ref(p), n);
t1.join();
t2.join();
cout << p->_year << endl;
cout << p->_month << endl;
cout << p->_day << endl;
cout << p.use_count() << endl;
return 0;
}
加锁后线程是安全的,引用计数一直都1,但是有点小问题是锁是在堆上,还需要释放
#include<iostream>
using namespace std;
#include<memory>
//
//#include <vector>
//void _MergeSort(int* a, int left, int right, int* tmp) {
// if (left >= right) return;
//
// int mid = left + ((right - left) >> 1);
// // [left, mid]
// // [mid+1, right]
// _MergeSort(a, left, mid, tmp);
// _MergeSort(a, mid + 1, right, tmp);
// int begin1 = left, end1 = mid;
// int begin2 = mid + 1, end2 = right;
// int index = left;
// while (begin1 <= end1 && begin2 <= end2)
// {
// if (a[begin1] < a[begin2])
// tmp[index++] = a[begin1++];
// else
// tmp[index++] = a[begin2++];
// }
// while (begin1 <= end1)
// tmp[index++] = a[begin1++];
// while (begin2 <= end2)
// tmp[index++] = a[begin2++];
// memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
//}
//void MergeSort(int* a, int n) {
// int* tmp = (int*)malloc(sizeof(int) * n);
// _MergeSort(a, 0, n - 1, tmp);
//
// // 这里假设处理了一些其他逻辑
// vector<int> v(1000000000, 10);
// // ...
//
// // free(tmp);
//}
//int main()
//{
// int a[5] = { 4, 5, 2, 3, 1 };
// MergeSort(a, 5);
// return 0;
//}
//
//
//void dev()
//{
// int x, y;
// cin >> x >> y;
// if (y == 0)
// throw "除0错误";
//}
//int main()
//{
// int* p1 = new int;
// try
// {
// dev();
// }
// catch(exception & e)
// {
// cout << "抛异常" << endl;
// }
//
//}
//template<class T>
//class SmartPtr
//{
//public:
// SmartPtr(T* ptr)
// :_ptr(ptr)
// {
//
// }
// ~SmartPtr()
// {
// delete _ptr;
// _ptr = nullptr;
// }
//private:
// T* _ptr;
//};
//int main()
//{
// SmartPtr<int> sp(new int);
//}
//namespace MyPtr
//{
// template<class T>
// class auto_ptr
// {
// public:
// auto_ptr(T* ptr)
// :_ptr(ptr)
// {
//
// }
// auto_ptr(auto_ptr<T>& sp) //管理权转移
// {
// _ptr = sp._ptr;
// sp._ptr = nullptr;
// }
// T& operator*()
// {
// return *_ptr;
// }
// T* operator->()
// {
// return _ptr;
// }
// ~auto_ptr()
// {
// if (_ptr)
// {
// cout << "delete" << &_ptr << endl;
// delete _ptr;
// }
//
// }
// private:
// T* _ptr;
// };
//
//}
//
//int main()
//{
// /*MyPtr::auto_ptr<int> sp1(new int);
// MyPtr::auto_ptr<int> sp2(sp1);
//
// cout << *sp1 << endl;*/
//
// auto_ptr<int> sp1(new int);
// auto_ptr<int> sp2(sp1);
// cout << *sp1 << endl;
//
//}
//
//class Date
//{
//public:
//};
//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;
//}
//namespace MyPtr
//{
// template<class T>
// class share_ptr
// {
// public:
// share_ptr(T* ptr)
// :_ptr(ptr)
// {
// _pCount = 1;
// }
// share_ptr(const share_ptr<T>& sp)
// :_ptr(sp._ptr)
// {
// ++_pCount;
// }
// share_ptr<T>& operator=(const share_ptr<T>& sp)
// {
// _ptr = sp._ptr;
// ++_pCount;
// return *this;
// }
// T& operator*()
// {
// return *_ptr;
// }
// T* operator->()
// {
// return _ptr;
// }
// ~share_ptr()
// {
// if (--_pCount == 0 && _ptr)
// {
// cout << "delete:" << &_ptr << endl;
// delete _ptr;
// }
// }
// private:
// T* _ptr;
// static int _pCount;
// };
// template<class T>
// int share_ptr<T>::_pCount = 0;
//}
//int main()
//{
// MyPtr::share_ptr<int> sp1(new int);
// MyPtr::share_ptr<int> sp2(sp1);
// MyPtr::share_ptr<int> sp3(sp1);
// //上面3个指针管理同一块资源,引用计数为3
//
// MyPtr::share_ptr<int> sp4(new int);
// //下面3个指针重新申请是把引用计数修改为1,明显错误
//}
#include<memory>
#include<thread>
#include<mutex>
class Date
{
public:
int _year = 0;
int _month = 0;
int _day = 0;
};
namespace MyPtr
{
template<class T>
class share_ptr
{
public:
share_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_pCount(new int(1))
,_mtx(new mutex)
{
}
share_ptr(const share_ptr<T>& sp)
:_ptr(sp._ptr)
,_pCount(sp._pCount)
,_mtx(sp._mtx)
{
//++(*_pCount);
AddRef();
}
share_ptr<T>& operator=(const share_ptr<T>& sp)
{
//if(this != &sp)
if (_ptr != sp._ptr) //要用原始指针判断,用类判断自己给自己赋值有问题
{
//if (--(*_pCount) == 0 && _ptr) //要释放以前的资源
//{
// cout << "delete:" << &_ptr <<endl;
// delete _ptr;
// delete _pCount;
//}
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
_mtx = sp._mtx;
//++(*_pCount);
AddRef();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
void Release()
{
_mtx->lock();
bool falg = false;
if (--(*_pCount) == 0) //要释放以前的资源
{
cout << "delete:" << &_ptr << endl;
delete _ptr;
delete _pCount;
falg = true;
}
_mtx->unlock();
if (falg)
delete _mtx;
}
int use_count()
{
return *_pCount;
}
void AddRef()
{
_mtx->lock();
++(*_pCount);
_mtx->unlock();
}
~share_ptr()
{
/*if (--(*_pCount) == 0 && _ptr)
{
cout << "delete:" << &_ptr << endl;
delete _ptr;
}*/
Release();
}
private:
T* _ptr;
int *_pCount;
mutex* _mtx;
};
}
//share_ptr是线程安全的,指在share_ptr内部引用计数是线程安全的,在外面线程安全是需要程序员自己手动保证
//线程安全的,所以在实现share_ptr是需要保证线程安全
void SharePtrFunc(MyPtr::share_ptr<Date>& sp, size_t n)
{
//cout << sp.Get() << endl;
for (size_t i = 0; i < n; ++i)
{
// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
MyPtr::share_ptr<Date> copy(sp);
// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但
//是最终看到的结果,并一定是加了2n
copy->_year++;
copy->_month++;
copy->_day++;
}
}
int main()
{
MyPtr::share_ptr<Date> p(new Date);
//cout << p.Get() << endl;
const size_t n = 10000;
thread t1(SharePtrFunc, std::ref(p), n);
thread t2(SharePtrFunc, std::ref(p), n);
t1.join();
t2.join();
cout << p->_year << endl;
cout << p->_month << endl;
cout << p->_day << endl;
cout << p.use_count() << endl;
return 0;
}
但是shared_ptr不能解决所有问题。
struct ListNode
{
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0; }
循环引用分析:
1. node1
和
node2
两个智能指针对象指向两个节点,引用计数变成
1
,我们不需要手动
delete
。
2. node1
的
_next
指向
node2
,
node2
的
_prev
指向
node1
,引用计数变成
2
。
3. node1
和
node2
析构,引用计数减到
1
,但是
_next
还指向下一个节点。但是
_prev
还指向上一个节点。
4.
也就是说
_next
析构了,
node2
就释放了。
5.
也就是说
_prev
析构了,
node1
就释放了。
6.
但是
_next
属于
node
的成员,
node1
释放了,
_next
才会析构,而
node1
由
_prev
管理,
_prev属于node2成员,所以这就叫循环引用,谁也不会释放。
// 解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
// 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加
node1和node2的引用计数。
struct ListNode
{
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0; }
如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这个问题,C++设计定制删除器来删除。
unique_ptr的删除器
class A
{
public:
};
struct DeleteArray
{
void operator()(A* ptr)
{
delete[] ptr;
}
};
int main()
{
unique_ptr<A> sp(new A);
unique_ptr<A, DeleteArray> sp2(new A[10]);
}
unique_ptr的定制删除器是在模板参数位置传入。而shared_ptr的定制删除器是在构造函数位置出传入。
class A
{
public:
};
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
int main()
{
//unique_ptr<A> sp(new A);
//unique_ptr<A, DeleteArray> sp2(new A[10]);
shared_ptr<A> sp(new A);
shared_ptr<A> sp1(new A[10], DeleteArray<A>());
}
4.C++11和boost中智能指针的关系
1. C++ 98
中产生了第一个智能指针
auto_ptr.
2. C++ boost
给出了更实用的
scoped_ptr
和
shared_ptr
和
weak_ptr.
3. C++ TR1
,引入了
shared_ptr
等。不过注意的是
TR1
并不是标准版。
4. C++ 11
,引入了
unique_ptr
和
shared_ptr
和
weak_ptr
。需要注意的是
unique_ptr
对应
boost
的
scoped_ptr
。并且这些智能指针的实现原理是参考
boost
中的实现的
5.RAII扩展学习
#include <thread>
#include <mutex>
// C++11的库中也有一个lock_guard,下面的LockGuard造轮子其实就是为了学习他的原理
template<class Mutex>
class LockGuard
{
public:
LockGuard(Mutex& mtx)
:_mutex(mtx)
{
_mutex.lock();
}
~LockGuard()
{
_mutex.unlock();
}
LockGuard(const LockGuard<Mutex>&) = delete;
private:
// 注意这里必须使用引用,否则锁的就不是一个互斥量对象
Mutex& _mutex;
};
mutex mtx;
static int n = 0;
void Func()
{
for (size_t i = 0; i < 1000000; ++i)
{
LockGuard<mutex> lock(mtx);
++n;
}
}
int main()
{
int begin = clock();
thread t1(Func);
thread t2(Func);
t1.join();
t2.join();
int end = clock();
cout << n << endl;
cout <<"cost time:" <<end - begin << endl;
return 0; }