在C语言学习的时候,指针的功能虽然强大,但是也是十分危险,指针的释放和置空在C语言中十分重要,不正确的操作可能会导致内存泄露。那能否让程序自己去控制在不需要资源时自动将其归还给系统。
RAII (Resource Acquisition IsInitialization) :
定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。这一技术常称为“资源分配即初始化”,简称 RAII。
智能指针: RAII思想的一种实现。
1.构造函数保存资源,析构函数释放资源。
2.想指针一样使用。
智能指针的提出和完成不是一蹴而就的,本文将通过在智能指针使用时遇到的问题,并将其逐步解决来一步步
解释说明C++中的指针的原理,在下文中将模拟实现C++提供的智能指针的部分代码,以便说明问题。
因为智能指针一开始的 目的很简单:资源分配了,忘记释放,从而导致了内存泄露的问题。
auto-ptr:解决了基本的资源释放的问题,但是依然有缺陷,不建议使用。
缺陷:因为没有考虑引用计数,所以只有唯一的拥有者。即一个对象只能由一个auto_ptr管理,在给其他的
auto_ptr赋值(复制)、参数传递时,才会转移这种关系,auto_ptr有三种实现方式:
第一种实现: 管理权转移 --- 在赋值运算符的重载和拷贝构造函数中把原来的指针赋值个新的,再把原来的
指针置空。
这种方法存在两个隐患:
1.因为采用的方法是把上一个指针置为空,就导致并不符合智能指针的两个基本原则:想指针一样。
2.因为将上一个指针置空,就导致可能访问空指针。
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{
cout << "Auto()" << endl;
}
AutoPtr(AutoPtr<T>& ap) //拷贝构造中将上一个指针置为空
:_ptr(ap._ptr)
{
ap._ptr = NULL;
}
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
if (_ptr != ap._ptr)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
T& operator *()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~AutoPtr()
{
if (NULL != _ptr)
{
cout << "Auto delete: 0x%p" << _ptr << endl;
delete _ptr;
_ptr = NULL;
}
}
T* GetPtr()
{
return _ptr;
}
private:
T* _ptr;
};
第二种实现: 由于置空可能会导致的问题,引入了标记owner,表示当前资源的实际拥有者。
缺点:在进行参数传递的时候,对生命周期短的对象赋值,因为owner的转移,在函数调用结束后,生命周期短的被析构,
当再次访问生命周期长的的时候,就会变成了野指针的访问
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
, _owner(true)
{
cout << "Auto()" << endl;
}
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._owner = false;
_owner = true;
}
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
if (_ptr != ap._ptr)
{
delete _ptr;
_ptr = ap._ptr;
ap._owner = false;
_owner = true;
}
return *this;
}
T& operator *()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~AutoPtr()
{
if (_ptr && _owner)
{
cout << "Auto delete: 0x%p" << _ptr << endl;
delete _ptr;
_ptr = NULL;
}
}
private:
T* _ptr;
bool _owner; //置空可能会导致解引用出现错误,这里为了解决引入__owner
};
第三种实现:在第一种实现的基础上运用了类型转换。(这里不进行实现)。
因为auto_ptr的种种弊病,在实际应用中几乎不会使用到auto_ptr。
scoped_ptr:粗暴地解决了因为指针的赋值导致不同的指针指向同一块内存,可能导致内存的重复释放。scoped_ptr阻止了指针的拷贝。
可以方便的管理单个的堆内存对象。且独享所有权。
template <class T>
class ScopePtr
{
public:
ScopePtr(T* ptr)
:_ptr(ptr)
{
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~ScopePtr()
{
if (_ptr)
{
delete _ptr;
cout << "~ScopePtr() -- " << _ptr <<endl;
_ptr = NULL;
}
}
private:
ScopePtr(const ScopePtr<T>& sp); //防拷贝:只声明,不定义;声明成私有
ScopePtr<T>& operator=(const ScopePtr<T>& sp);
protected:
T* _ptr;
};
template <class T>
class ScopedArray
{
public:
ScopedArray(T* ptr)
:_ptr(ptr)
{
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
~ScopedArray()
{
if (_ptr)
{
delete _ptr;
cout << "~ScopedArray() -- " << _ptr << endl;
_ptr = NULL;
}
}
private:
ScopedArray(const ScopePtr<T>& sp); //防拷贝:只声明,不定义;声明成私有
ScopedArray<T>& operator=(const ScopedArray<T>& sp);
protected:
T* _ptr;
};
shared_ptr:允许拷贝和赋值,引入了引用计数,解决了指针建对象的管理权的问题。
缺陷:
1.需要定制删除器,即指针指向不同类型的变量,析构时的方式也不一样。
2.写出的代码不是线程安全的。
3.存在循环引用的问题。双向循环链表--- 前驱结点的next指针指向后继结点,后继结点的prev指针指向前驱结点。因为两个指针的释放都依赖于对方的释放,于是就造成了循环引用的问题。
template <class T>
class SharedPtr
{
friend class WeakPtr<T>;
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _Count(new int(1))
{}
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
, _Count(sp._Count)
{
++(*_Count);
}
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
if (_ptr != sp._ptr)
{
if (--(*_Count) == 0)
{
delete _ptr;
delete _Count;
}
_ptr = sp._ptr;
_Count = sp._Count;
(*sp._Count)++;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~SharedPtr()
{
if (--(*_Count) == 0)
{
delete _Count;
if (_ptr)
{
delete _ptr;
cout << "~SharedPtr() -- " << _ptr << endl;
_ptr = NULL;
}
}
}
protected:
T* _ptr;
int* _Count;
};
template <class T>
class SharedArray
{
public:
SharedArray(T* ptr)
:_ptr(ptr)
, _Count(new int(1))
{}
SharedArray(const SharedArray<T>& sp)
:_ptr(sp._ptr)
, _Count(sp._Count)
{
++(*_Count);
}
SharedArray<T>& operator=(const SharedArray<T>& sp)
{
if (_ptr != sp._ptr)
{
if (--(*_Count) == 0)
{
delete _ptr;
delete _Count;
}
_ptr = sp._ptr;
_Count = sp._Count;
(*sp._Count)++;
}
return *this;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
~SharedArray()
{
if (--(*_Count) == 0)
{
delete _Count;
if (_ptr)
{
delete[] _ptr;
cout << "~SharedArray() -- " << _ptr << endl;
_ptr = NULL;
}
}
}
protected:
T* _ptr;
int* _Count;
};
循环引用问题:
struct listnode
{
sharedptr<listnode> _next;
sharedptr<listnode> _prev;
int _data;
listnode()
:_next(null)
,_prev(null)
,_data(0)
{
}
};
void testsharedptr()
{
sharedptr<listnode> node1 = new listnode;
sharedptr<listnode> node2 = new listnode;
node1->_next = node2;
node2->_prev = node1;
}
weak_ptr: 为了配合shared_ptr而引入的一种智能指针,为了解决shared_ptr的循环引用问题,它并不是用来管理空间资源的。
template<class T>
class WeakPtr;
template <class T>
class SharedPtr
{
friend class WeakPtr<T>;
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _Count(new int(1))
{}
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
, _Count(sp._Count)
{
++(*_Count);
}
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
if (_ptr != sp._ptr)
{
if (--(*_Count) == 0)
{
delete _ptr;
delete _Count;
}
_ptr = sp._ptr;
_Count = sp._Count;
(*sp._Count)++;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~SharedPtr()
{
if (--(*_Count) == 0)
{
delete _Count;
if (_ptr)
{
delete _ptr;
cout << "~SharedPtr() -- " << _ptr << endl;
_ptr = NULL;
}
}
}
protected:
T* _ptr;
int* _Count;
};
/解决循环引用 引入WeakPtr --
template<class T>
class WeakPtr
{
public:
WeakPtr(const SharedPtr<T>& sp) //不接受T*
:_ptr(sp._ptr)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};