C++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被11弃用。
为什么要使用智能指针:
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
1. auto_ptr(c++98的方案,cpp11已经抛弃)
采用所有权模式。
auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错.
此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!
2. unique_ptr(替换auto_ptr)
unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。
采用所有权模式,还是上面那个例子
unique_ptr<string> p3 (new string ("auto")); //#4
unique_ptr<string> p4; //#5
p4 = p3;//此时会报错!!
编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。
另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:
unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; // #1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You")); // #2 allowed
其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。
注:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。例如:
unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;
3. shared_ptr
shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。
成员函数:
use_count 返回引用计数的个数
unique 返回是否是独占所有权( use_count 为 1)
swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr<int> sp(new int(1)); sp 与 sp.get()是等价的
4. weak_ptr
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
class B;
class A
{
public:
shared_ptr<B> pb_;
~A()
{
cout<<"A delete\n";
}
};
class B
{
public:
shared_ptr<A> pa_;
~B()
{
cout<<"B delete\n";
}
};
void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->pa_ = pa;
pa->pb_ = pb;
cout<<pb.use_count()<<endl;
cout<<pa.use_count()<<endl;
}
int main()
{
fun();
return 0;
}
可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用),如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_; 改为weak_ptr pb_; 运行结果如下,这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。
注意的是我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(); 英文pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();
下面是基于引用计数的代码实现
#include <iostream>
using namespace std;
template<class T>
class SmartPtr
{
public:
SmartPtr(T *p);
~SmartPtr();
SmartPtr(const SmartPtr<T> &orig); // 浅拷贝
SmartPtr<T>& operator=(const SmartPtr<T> &rhs); // 浅拷贝
private:
T *ptr;
// 将use_count声明成指针是为了方便对其的递增或递减操作
int *use_count;
};
template<class T>
SmartPtr<T>::SmartPtr(T *p) : ptr(p)
{
try
{
use_count = new int(1);
}
catch (...)
{
delete ptr;
ptr = nullptr;
use_count = nullptr;
cout << "Allocate memory for use_count fails." << endl;
exit(1);
}
// cout << "Constructor is called!" << endl;
}
template<class T>
SmartPtr<T>::~SmartPtr()
{
// 只在最后一个对象引用ptr时才释放内存
if (--(*use_count) == 0)
{
delete ptr;
delete use_count;
ptr = nullptr;
use_count = nullptr;
// cout << "Destructor is called!" << endl;
}
}
template<class T>
SmartPtr<T>::SmartPtr(const SmartPtr<T> &orig)
{
ptr = orig.ptr;
use_count = orig.use_count;
++(*use_count);
// cout << "Copy constructor is called!" << endl;
}
// 重载等号函数不同于复制构造函数,即等号左边的对象可能已经指向某块内存。
// 这样,我们就得先判断左边对象指向的内存已经被引用的次数。如果次数为1,
// 表明我们可以释放这块内存;反之则不释放,由其他对象来释放。
template<class T>
SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<T> &rhs)
{
// 《C++ primer》:“这个赋值操作符在减少左操作数的使用计数之前使rhs的使用计数加1,
// 从而防止自身赋值”而导致的提早释放内存
++(*rhs.use_count);
// 将左操作数对象的使用计数减1,若该对象的使用计数减至0,则删除该对象
if (--(*use_count) == 0)
{
delete ptr;
delete use_count;
// cout << "Left side object is deleted!" << endl;
}
ptr = rhs.ptr;
use_count = rhs.use_count;
// cout << "Assignment operator overloaded is called!" << endl;
return *this;
}
或者参考一下这个
template<class T>
class SharedPtr
{
public:
SharedPtr(SharedPtr<T>* sp,int* spcount)
:_ptr(sp)
, _pCount(spcount)
{}
SharedPtr(const SharedPtr<T>& sp)
{
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*pCount);
}
SharedPtr& opertaor=(const SharedPtr<T>& sp)
{
if (this != &sp)
{
if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
}
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*pCount);
}
return *this;
}
~SharedPtr()
{
if (--(*pCount) == 0)
{
delete _ptr;
delete _pCount;
}
}
T&operator*()
{
return *_ptr;
}
T*operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pCount;
};
弱引用是用来解决循环引用的问题的,那么什么是循环引用呢,下面开看一下:
struct ListNode
{
SharedPtr<ListNode> _next;
SharedPtr<ListNode> _prev;
ListNode()
:_prev(NULL)
,_next(NULL)
{}
~ListNode()
{
cout << "delete ListNode" << endl;
}
};
void TestSharePtrCycle()
{
SharedPtr<ListNode> cur = new ListNode;
SharedPtr<ListNode> next = new ListNode;
cur->_next = next;
next->_prev = cur;
}
图形解析
什么是弱指针
弱指针是指当指针指向原来空间时,引用计数不在进行加1
注:不是管理和释放当前的指针,而是避免了一次引用计数
如何解决循环引用
使用弱指针来负责cur->next 和next->prev的指向时,使其引用计数不在加1,这样两个节点的引用计数就是1,不会再增加,故此释放时,会将其节点进行释放
下面是代码实现
template<class T>
class WeakPtr;
template<class T>
class SharedPtr
{
friend class WeakPtr<T>;
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new int(1))
{}
SharedPtr(const SharedPtr<T>& sp)
{
_ptr = sp._ptr;
_pCount = sp._pCount;
(*_pCount)++;
}
// sp1 = sp2;
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
if (this != &sp)
{
if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
}
_pCount = sp._pCount;
_ptr = sp._ptr;
(*_pCount)++;
}
return *this;
}
~SharedPtr()
{
//cout<<"~SharedPtr()"<<endl;
if (--(*_pCount) == 0)
{
if (_ptr)
{
printf("ptr:%p\n", _ptr);
delete _ptr;
}
delete _pCount;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
int* _pCount;
};
// 专门解决SharedPtr的循环引用
// 弱指针是指当指针指向原来空间时,引用计数不在进行加1,释放时不在等待,故此解决了循环引用的问题
// 不是管理和释放当前的指针,而是避免了一次引用计数
template<class T>
class WeakPtr
{
public:
WeakPtr(SharedPtr<T>& sp)//可以将sharedptr的对像传给他,匿名对象
:_ptr(sp._ptr) //这里将sharedptr的对象给weakptr(弱指针)进行初始化,此时弱指针指向的是sharedptr的对象
{}
WeakPtr()
:_ptr(NULL)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
struct ListNode
{
WeakPtr<ListNode> _next;
WeakPtr<ListNode> _prev;
//ListNode()
// :_prev(NULL)
// ,_next(NULL)
//{}
~ListNode()
{
cout << "delete ListNode" << endl;
}
};
void TestSharePtrCycle()
{
SharedPtr<ListNode> cur = new ListNode;//使用弱指针
SharedPtr<ListNode> next = new ListNode;
cur->_next = next;//weakptr接收的是sharedptr的对象
next->_prev = cur;
}
下面是shared_ptr的详细代码:
//核心要理解引用计数,什么时候销毁底层指针,还有赋值,拷贝构造时候的引用计数的变化,析构的时候要判断底层指针的引用计数为0了才能真正释放底层指针的内存
template <typename T>
class SmartPtr
{
private:
T *ptr; //底层真实的指针
int *use_count;//保存当前对象被多少指针引用计数
public:
SmartPtr(T *p); //SmartPtr<int>p(new int(2));
SmartPtr(const SmartPtr<T>&orig); //SmartPtr<int>q(p);
SmartPtr<T>&operator=(const SmartPtr<T> &rhs); //q=p
~SmartPtr();
T operator*(); //为了能把智能指针当成普通指针操作定义解引用操作
T*operator->(); //定义取成员操作
T* operator+(int i); //定义指针加一个常数
int operator-(SmartPtr<T>&t1,SmartPtr<T>&t2);//定义两个指针相减
void getcount()
{
return *use_count
}
};
template <typename T>
int SmartPtr<T>::operator-(SmartPtr<T> &t1, SmartPtr<T> &t2)
{
return t1.ptr-t2.ptr;
}
template <typename T>
SmartPtr<T>::SmartPtr(T *p)
{
ptr=p;
try
{
use_count=new int(1);
}catch (...)
{
delete ptr; //申请失败释放真实指针和引用计数的内存
ptr= nullptr;
delete use_count;
use_count= nullptr;
}
}
template <typename T>
SmartPtr<T>::SmartPtr(const SmartPtr<T> &orig) //复制构造函数
{
use_count=orig.use_count;//引用计数保存在一块内存,所有的SmarPtr对象的引用计数都指向这里
this->ptr=orig.ptr;
++(*use_count);//当前对象的引用计数加1
}
template <typename T>
SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<T> &rhs)
{
//重载=运算符,例如SmartPtr<int>p,q; p=q;这个语句中,首先给q指向的对象的引用计数加1,因为p重新指向了q所指的对象,所以p需要先给原来的对象的引用计数减1,如果减一后为0,先释放掉p原来指向的内存,然后讲q指向的对象的引用计数加1后赋值给p
++*(rhs.use_count);
if((--*(use_count))==0)
{
delete ptr;
ptr= nullptr;
delete use_count;
use_count= nullptr;
}
ptr=rhs.ptr;
*use_count=*(rhs.use_count);
return *this;
}
template <typename T>
SmartPtr<T>::~SmartPtr()
{
getcount();
if(--(*use_count)==0) //SmartPtr的对象会在其生命周期结束的时候调用其析构函数,在析构函数中检测当前对象的引用计数是不是只有正在结束生命周期的这个SmartPtr引用,如果是,就释放掉,如果不是,就还有其他的SmartPtr引用当前对象,就等待其他的SmartPtr对象在其生命周期结束的时候调用析构函数释放
{
getcount();
delete ptr;
ptr= nullptr;
delete use_count;
use_count=nullptr;
}
}
template <typename T>
T SmartPtr<T>::operator*()
{
return *ptr;
}
template <typename T>
T* SmartPtr<T>::operator->()
{
return ptr;
}
template <typename T>
T* SmartPtr<T>::operator+(int i)
{
T *temp=ptr+i;
return temp;
}