智能指针
- 概念
所谓智能指针就是智能/自动的管理指针所指动态资源的释放。 现阶段的智能指针是一种通用实现技术,是使用引用技术。
- 发展历史
- 第一阶段(C++98):auto_ptr ----自动指针
auto_ptr 的主要思想是 管理权转移, 但其缺陷很大,当通过拷贝构造函数,通过操作符=赋值后,原来的那个智能指针对象就失效了。只有新的智能 指针对象可以有效使用了。
- 第二阶段(C++03):即在Boost中的智能指针
scoped_ptr —— 守卫指针 scoped_ptr的主要思想是 防拷贝
share_ptr —— 共享指针 share_ptr的主要思想是 引用计数复杂 缺陷:循环引用
weak_ptr —— 弱智者,不能单独存在,辅助解决shared_ptr的循环引用问题
- 第三阶段(C++11):
unique_ptr 还是运用防拷贝的思想
share_ptr
weak_ptr----弱指针,不能单独存在,辅助解决shared_ptr的循环引用问题
实现
- AutoPtr
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{}
~AutoPtr()
{
cout<<"指针被释放"<<endl;
if(_ptr)
{
delete _ptr;
}
}
//运算符的重载(像指针一样)
T& operator* ()
{
return *_ptr;//*(this->_ptr)
}
T* operator->()
{
return _ptr;
}
//拷贝构造
AutoPtr(AutoPtr<T>& ap)//管理权转移
:_ptr(ap._ptr)
{
ap._ptr = NULL;
}
//ap1=ap2
AutoPtr<T>& operator=(AutoPtr<T> &ap)
{
if(this != &ap)
{
if(_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
private:
T* _ptr;
};
分析:
这里是因为 ap2 完全的夺取了 ap1 的管理权。然后导致访问 ap1 的时候程序就会崩溃。
如果在这里调用 ap2 = ap1 程序一样会崩溃,原因还是 ap1 被夺走管理权,所以这种编程
思想及其不符合C++思想,所以它的设计思想就是有一定的缺陷。因此,一般不推荐使用 auto_ptr 智能指针。
ScopedPtr
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
~ScopedPtr()
{
if(_ptr)
{
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
ScopedPtr(const ScopedPtr<T> &sp);//不能定义为公有的,防止在类外实现
ScopedPtr<T>& operator=(const ScopedPtr<T> &sp);
private:
T *_ptr;
};
注: ScopedPtr
将拷贝构造和赋值运算符的重载定义为protected,
并且只声明不实现,这样就实现了 防拷贝 的原理。
- SharedPtr
template<class T>
class SharedPtr
{
friend class WeakPtr<T>;
public:
SharedPtr(T* ptr)
:_ptr(ptr)
,_refcount(new int(1))
{}
~SharedPtr()//当引用技术为0时,释放空间
{
cout<<"~SharedPtr()"<<endl;
if(--(*_refcount)==0)
{
printf("ox%p\n",_ptr);
delete _ptr;
delete _refcount;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//sp2(sp1)
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
,_refcount(sp._refcount)
{
(*_refcount)++;
}
//sp1 = sp2
SharedPtr<T> &operator = (const SharedPtr<T>& sp)
{
if(_ptr != sp._ptr) //防止自已给自已赋值
{
if(--(*_refcount)==0)
{
delete _ptr;
delete _refcount;
}
_ptr = sp._ptr;
_refcount = sp._refcount ;
(*_refcount)++;
}
return *this;
}
private:
T* _ptr;
int* _refcount;//引用次数
};
注:
share_ptr 因为它采用引用计数的思想,所有它是功能较为完善的,但是 share_ptr 还是有缺陷的,例如在解决
循环引用
问题上。
eg: 双向链表
(1) 无智能指针的双向链表
struct ListNode
{
int _data;
ListNode* _next;
ListNode* _prev;
~ListNode()
{
cout<<"ListNode"<<endl;
}
};
void TestRec()
{
ListNode* cur = new ListNode;
ListNode* next = new ListNode;
cur->_next = next;
next->_prev = cur;
delete cur;
delete next;
}
(2)含有智能指针的双向链表
struct ListNode
{
int _data;
SharedPtr<ListNode> _next;
SharedPtr<ListNode> _prev;
ListNode()
:_data(0)
,_next(NULL)
,_prev(NULL)
{}
~ListNode()
{
cout<<"ListNode"<<endl;
}
};
void TestRec()
{
SharedPtr<ListNode> cur(new ListNode);
SharedPtr<ListNode> next(new ListNode);
cur->_next = next; //加上这两句导致循环引用
next->_prev = cur;
}
分析: 由运行结果知,智能指针cur、next并没有释放,若加cur->_next = next; next->_prev = cur; 这两句运行结果如下:
由结果可知,由于上面两句导致循环引用。
画图分析如下:
_next、_prev的生命周期依赖于结点的释放,而cur、next结点又依赖于_next、_prev的释放,所以导致循环引用。
解决方法:
使用弱引用的智能指针打破这种循环引用。
- WeakPtr
weak_ptr被设计为与shared_ptr共同工作,可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。
template <class T>
class WeakPtr
{
public:
WeakPtr()
:_ptr(NULL)
{}
WeakPtr(const SharedPtr<T> & sp)
:_ptr(sp._ptr)
{}
WeakPtr<T>& operator=(const SharedPtr<T>& sp)
{
_ptr = sp._ptr;
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
struct ListNode
{
int _data;
WeakPtr <ListNode> _next;
WeakPtr <ListNode> _prev;
ListNode()
:_data(0)
,_next(NULL)
,_prev(NULL)
{}
~ListNode()
{
cout<<"~ListNode"<<endl;
}
};
void TestRec()
{
SharedPtr<ListNode> cur(new ListNode);
SharedPtr<ListNode> next(new ListNode);
cur->_next = next;
next->_prev = cur;
}
由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。
注:虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。