c++中我们常常使用运算符new和delete来分配和释放动态内存,然而动态内存的管理非常容易出错
使用new 和delete 管理内存存在三个常见问题:
1.忘记delete(释放) 内存。(或者异常导致程序过早退出,没有执行 delete)忘记释放动态内存会导致人们常说的 内存泄露 问题,你申请了内存而为归还给操作系统长时间这样会导致系统内存越来越小。
(内存泄露问题往往很难查找到,内存耗尽时,才能检测出这种错误)
2.使用已经释放掉的对象。比如:我们使用delete释放掉申请的内存空间,但并未干掉指向这片空间的指针,此时指针指向的就是“垃圾”内存。
3.同一块内存释放两次。当有两个指针指向相同的动态内存分配对象时,其中一个进行了delete操作 对象内存就还给了操作系统 ,如果我们要delete第二个指针,那么内存有可能遭到破坏(浅拷贝问题)
为了更容易同时也更安全的使用动态内存标准库为我们提供了 智能指针 来管理动态对象,智能指针的行为类似常规的指针,但是它并非指针,它是一个存储指向动态分配(堆)对象指针的类。
这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针(释放管理的堆空间)。智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放
因为智能指针行为类似于指针 所以一般的指针行为智能指针也具备(每个智能指针重载了->和 *)
T& opertaor*(){
return *_ptr;
} //这里返回了一个对象T 我们需要对T进行改动 所以返回T&
T* operator->(){
return _ptr;
} //这里相当于是返回了一个指针,然后在使用这个指针指向一个内容
话不多说首先让我们来看第一种智能指针auto_ptr(管理权限转让)
我们经常会遇到这种问题比如说浅拷贝的时候多个指针管理空间 但是当其中一个指针先结束释放了这块空间 ,那么当其他指针结束时在对这片空间进行释放 程序就会崩溃。
为了方便解决上述问题我就可以使用智能指针auto_ptr 其主要原理是是在构造对象时赋予其管理空间的所有权,在拷贝或赋值中转移空间的所有权 拷贝和赋值后直接将_ptr赋为空,禁止其再次访问原来的内存空间。
//auto_ptr的简单实现
template<class T>
class Autoptr
{
public:
Autoptr(T* ptr=NULL)
:_ptr(ptr)
{}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
Autoptr(Autoptr<T>& ap){ //拷贝构造
this->_ptr = ap._ptr;
ap._ptr = NULL;
}
Autoptr<T>& operator=(Autoptr<T>& ap){
if (this != &ap){
delete this->_ptr;
this->_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~Autoptr(){
if (_ptr){
cout << "释放空间le" << endl;
delete _ptr;
}
}
private:
T* _ptr;
};
int main(){
//Autoptr<int>ap1(new int);
//*ap1 = 10;
//Autoptr<int>ap2(ap1);
//Autoptr<int>ap3(ap2);
//*ap3 = 20;
//ap2 = ap3;
//cout << *ap2 << endl;
Autoptr<int>ap1(new int);
*ap1 = 10;
Autoptr<int>ap2(ap1);
cout << *ap1 << endl;//调试到这一步程序崩溃了,罪魁祸首就是 AutoPtr<int>ap2(ap1),
//这里原因就是ap2完全的夺取了ap1的管理权。
//导致ap1被置为NULL,访问它的时候程序就会崩溃。
system("pause");
return 0;
}
由于它实现了完全的权限转移,所以导致在拷贝构造和赋值之后只有一个指针可以使用,而其他指针都置为NULL,使用很不方便,而且还很容易对NULL指针进行解引用,导致程序崩溃,其危害也是比较大的。
为了解决 auto_ptr 带来的问题另一种智能指针横空出世—–scoped_ptr(防拷贝) 它的实现原理是直接将拷贝构造和赋值运算符设置为私有或保护只声明不定义
防止他人在类外定义,这样一次就只有一个指针对空间进行管理就不会出现上面的问题。
scopd_ptr ( unique_ptr )
//Scoped_ptr 独占资源
template<class T>
class Scoped_ptr
{
public:
Scoped_ptr(T* _Ptr=NULL)
:_ptr(ptr)
{}
T& operator*(){
return *_ptr;
}
T* operator->(){
return _ptr;
}
~Scoped_ptr()
{
if (_ptr){
delete _ptr;
}
}
private:
Scoped_ptr(const Scoped_ptr<T>&);
//{}
Scoped_ptr<T>& operator=(Scoped_ptr<T>& ap);
private:
T* _ptr;
};
//int main(){
//Scoped_ptr<int> sp1(new int);
//Scoped_ptr<int> sp2(sp1); 不可以进行拷贝构造 此函数设置为私有 不可进行访问
//system("pause");
//return 0;
//}
第三种智能指针shared_ptr (引用计数版本)允许多个指针指向同一对象
其原理是 通过引用计数记录对当前操作的指针的个数当进行拷贝构造或者赋值时_pCount++, 析构时当_pCount为0时才进行释放空间。
//简单实现
template<class T>
class Shared_ptr{
public:
Shared_ptr(T* ptr = NUll)
:_ptr(ptr)
, _pCount(new int(1))
{}
/*{
if (_ptr){
_pCount = new int(1);
}
}*/
Shared_ptr(const Shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pCount(sp._pCount)
{
++GetRef();
}
//当进行拷贝或赋值操作时 每个shared_ptr都会有一个计数器 记录着和它指向相同空间的shared_ptr的个数
Shared_ptr<T>& operator=(const Shared_ptr<T>& sp){
if (this != &sp)
{
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
++GetRef();
}
return *this;
}
T& operator*(){
return *_ptr;
}
T* operator->(){
return _ptr;
}
~Shared_ptr(){
Release();
}//当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类才会自动销毁此对象
int Usecount(){
return GetRef();
}
private:
void Release(){
if (0 == --*_pCount &&_ptr){
delete _ptr;
delete _pCount;
_ptr = NULL;
_pCount = NULL;
}
}
int& GetRef(){
return *_pCount;
}
private:
T* _ptr;
T* _pCount;
};
/*int main(){
Shared_ptr<int> sp1(new int(10));
Shared_ptr<int> sp2(sp1);
*sp1 = 10;
*sp2 = 20;
cout << sp1.Usecount() << endl;
cout << sp2.Usecount() << endl;
Shared_ptr<int> sp3(new int(30));
Shared_ptr<int> sp4(sp3);
sp4 = sp2;
*sp4 = 40;
cout << sp3.Usecount() << endl;
cout << sp4.Usecount() << endl;
system("pause");
return 0;
}*/
看完shared_ptr基本原理 我们在来分析分析它的缺陷(循环引用问题)
例:
#include<memory>
template<class T>
struct ListNode{
//ListNode<T>* _next;
shared_ptr<ListNode<T>> _next;
//ListNode<T>* _prev;
shared_ptr<ListNode<T>> _prev;
T _data;
ListNode(const T& data = T())
:_next(NULL)
, _prev(NULL)
, _data(data)
{
cout << "LisNode(const T& data)" << this << endl;
}
~ListNode(){
cout << "~ListNode():" << this << endl;
}
};
void Testshared_ptr(){
shared_ptr<ListNode<int>> p1(new ListNode<int>(10));
shared_ptr<ListNode<int>> p2(new ListNode<int>(20));
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
p1->_next = p2;
p2->_prev = p1;
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
}
int main(){
Testshared_ptr();
system("pause");
return 0;
}
为了解决shared_ptr中的循环引用问题 我们引入shared_ptr的助手指针 weak_ptr 我们来看看代码怎么改
#include<memory>
template<class T>
struct ListNode{
//ListNode<T>* _next;
weak_ptr<ListNode<T>> _next;
//shared_ptr<ListNode<T>> _next;
//ListNode<T>* _prev;
weak_ptr<ListNode<T>> _prev;
//shared_ptr<ListNode<T>> _prev;
T _data;
ListNode(const T& data = T())
/*:_next(NULL)
, _prev(NULL)
, _data(data)*/
:_data(data)
{
cout << "LisNode(const T& data):" << this << endl;
}
~ListNode(){
cout << "~ListNode():" << this << endl;
}
};
weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。同时,weak_ptr 必须从一个share_ptr或者另一个weak_ptr转换而来,不能使用new 对象进行构造。由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。