智能指针
今天我们主要就是来说一下智能指针,在前面的c语言阶段,我们就没有接触过这些东西,但是通过对c++知识的进一步了解,我们会发现c++存在内存泄露问题和在上一篇博客中提到的异常问题。
1.智能指针的引入
首先来看一下这段代码,分析一下他有没有什么内存问题。
#include <iostream>
#include <vector>
using namespace std;
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left >= right)
return;
int mid = left + ((right - left) >> 1);
_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);
}
int main()
{
int a[5] = { 4, 5, 2, 3, 1 };
Mergesort(a, 5);
return 0;
}
问题分析:
1.malloc出来的空间没有进行释放,存在内存泄漏问题
2.异常安全问题,如果在malloc和free之间存在抛异常,那么还是内存泄漏问题。
2.智能指针的使用
通过上面的代码我们可以看出,智能指针在C++中的应用,是主要针对内存泄漏问题,
2.1 RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源 接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
1、不需要显示的释放资源
2、采用这种方式,对象所需的资源在其生命期内始终保持有效。
RAII的优缺点:
优势:用户不需要关心资源何时去释放
缺陷:浅拷贝----不能采用深拷贝的方式解决
2.2 智能指针的原理
智能指针原理:RAII + 类对象具有类似指针的行为(operator*()/operator->()) + 解决浅拷贝
template<class T>
class Smartptr
{
public:
Smartptr(T* ptr = nullptr)
:_ptr(ptr)
{}
~Smartptr()
{
if (_ptr)
delete _ptr;
}
//让该类的对象具有指针类似的行为
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
struct A
{
int a;
int b;
int c;
};
void TestFunc1()
{
Smartptr<int> sp(new int);
*sp = 100;
//解引用
Smartptr<A> sp1(new A);
sp1->a = 10;
sp1->b = 20;
sp1->c = 30;
}
int main()
{
TestFunc1();
return 0;
}
3.智能指针的分类
3.1 auto_ptr
1.C++98版本为我们提供了auto_ptr的智能指针。
(1)原理:RAII + 类对象具有类似指针的行为(operator*()/operator->()) + 解决浅拷贝(资源的转移)
(2)存在的问题:当对象拷贝或复制后,前面的对象就悬空了,如果在访问前面的对象就会中断代码**。多个对象不能同时访问一份资源。**
#include <iostream>
using namespace std;
namespace bite
{
template<class T>
class auto_ptr
{
public:
//RAII
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap)
{
if (_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
delete _ptr;
}
}
//让类具有指针类似的行为
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
}
void TestFunc()
{
//常规指针
int a = 10;
int* p1 = &a;
int* p2(p1);
*p1 = 100;
*p2 = 200;
bite::auto_ptr<int> ap1(new int);
//解决方式:将资源转移
bite::auto_ptr<int> ap2(ap1);
*ap1 = 10;//会发生崩溃----ap1已经将资源转移给ap2,自已与资源断开联系
}
int main()
{
TestFunc();
return 0;
}
2.改进:auto_ptr
原理:RAII + 具有指针类似行为 + 解决浅拷贝:资环管理权的转移(对资源释放的权利)
实现方式:在auto_ptr类中维护bool类型变量
_owner:ture代表该对象具有对资源释放的权利
缺陷:存在野指针问题
namespace bite
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _owner(false)
{
if (_ptr)
_owner = true;
}
auto_ptr(const auto_ptr<T>& ap)
:_ptr(ap._ptr)
, _owner(ap._owner)
{
ap._owner = false;
}
auto_ptr<T>& operator=(const auto_ptr<T>& ap)
{
if (this != &ap)
{
//先处理以前旧资源
if (_ptr && _owner)
delete _ptr;
_ptr = ap._ptr;
_owner = ap._owner;
ap._owner = false;
}
return *this;
}
~auto_ptr()
{
if (_ptr && _owner)
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
mutable bool _owner; //资源真正拥有者(对资源具有释放的权利)
};
}
void TestFunc()
{
bite::auto_ptr<int> ap1(new int);
bite::auto_ptr<int> ap2(ap1);
bite::auto_ptr<int> ap3;
*ap1 = 10;
*ap2 = 20;
*ap3 = 30;
}
int main()
{
TestFunc();
return 0;
}
3.2 unique_ptr
unique_ptr智能指针原理:RAII + 具有指针类似行为 + 解决浅拷贝(资源独占的方式(一份资源只能被一个对象管理))
实现方式:禁止拷贝构造和赋值运算符重载调用—防拷贝
缺陷:多个unique_ptr的对象之间不能共享资源
#include <iostream>
using namespace std;
namespace bite
{
template<class T>
class unique_ptr
{
//RAII
public:
unique_ptr(T* ptr = nullptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
delete _ptr;
}
//具有指针类似行为
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//防止被拷贝--禁止调用拷贝构造&赋值运算符重载
#if 0
//C++98:只声明不定义& private
private:
unique_ptr(const unique_ptr<T>& up);
unique_ptr<T>& operator=(const unique_ptr<T>& up);
#endif
//C++11中
unique_ptr(const unique_ptr<T>& up) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
protected:
T* _ptr;
};
}
void TestFunc()
{
bite::unique_ptr<int> up1(new int);
bite::unique_ptr<int> up2;
}
int main()
{
TestFunc();
return 0;
}
3.3 shared_ptr
1.shared_ptr智能指针原理: RAII + 指针类似的行为 + 引用计数
2.缺陷:1. 不是线程安全 2. 只能管理new的资源 3.存在循环引用问题
#include <iostream>
#include <mutex>
using namespace std;
namespace bite
{
template < class T >
class shared_ptr
{
public:
//RAII
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pCount(nullptr)
, _pMutex(nullptr)
{
if (_ptr)
{
_pMutex = new mutex;
_pCount = new int(1);
}
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pCount(sp._pCount)
, _pMutex(sp._pMutex)
{
if (_pCount)
AddRef();
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (this != &sp)
{
//让当前对象与资源断开
Release();
//与sp共享
_ptr = sp._ptr;
_pCount = sp._pCount;
if (_pCount)
AddRef();
}
return *this;
}
~shared_ptr()
{
Release();
}
//具有指针类似行为
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int use_count()
{
return *_pCount;
}
private:
void AddRef()
{
_pMutex->lock();
++(*_pCount);
_pMutex->unlock();
}
int SubRef()
{
_pMutex->lock();
--(*_pCount);
_pMutex->unlock();
return *_pCount;
}
void Release()
{
if (_ptr && 0 == SubRef())
{
delete _ptr;
delete _pCount;
}
}
protected:
T* _ptr;
int* _pCount;
mutex* _pMutex; //保证计数的安全性,互斥锁
};
}
//mutex:可以保证计数的安全性 || 可以保证shared_ptr类本身的安全性
//但不能保证shared_ptr管理资源中内容的安全性
void TestFunc()
{
bite::shared_ptr<int> sp1(new int);
bite::shared_ptr<int> sp2(sp1);
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
bite::shared_ptr<int> sp3(new int);
bite::shared_ptr<int> sp4(sp3);
cout << sp3.use_count() << endl;
cout << sp4.use_count() << endl;
sp3 = sp1;
cout << sp1.use_count() << endl;
sp4 = sp1;
cout << sp1.use_count() << endl;
}
int main()
{
TestFunc();
return 0;
}
3.shared_ptr的线程安全问题
(1)智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2,这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以智能指针中引用计数++ 、–是需要加锁的,也就是说引用计数的操作是线程安全的。
(2)智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。