智能指针
(1)定义:
智能指针是行为类似于指针的类对象,但其功能多于指针。
说明:c++程序设计中经常会用堆内存,程序员要自己管理内存的申请和释放。使用原始指针,容易造成堆内存泄漏(忘记释放),二次释放;使用智能指针能更好的管理堆内存。智能指针本质是存放在栈的模板对象,只是在栈内部包了一层指针,其类内部有析构函数。而栈在其生命周期结束时,其中的指针指向的堆内存也自然被析构函数释放了。因而实现了智能管理的效果,不需要考虑内存问题了,其实有点类似某种单例写法,程序运行结束,也不用考虑单例对象内存问题。
本次讨论:c++11之前的auto_ptr; c++11新加的unique_ptr, shared_ptr。
(2)构造
模板类的构造:
template<class X> class auto_ptr
{
public:
explicit auto_ptr(X* p=0) throw();
}
模板类的实例化:
头文件:#include <memory>
auto_ptr<double> pd(new double);
auto_ptr<string> ps(new string);
auto_ptr<report> pr(new report(“this is a class”));
//report是一个类,可通过pr调用类的方法。
其中new double 是new返回的指针,指向新分配的内存块,new double也是构造函数的实参。
unique_ptr<double> pdu(new double);
shared_ptr<double>pds(new double);
(3)指针和智能指针的隐式转换问题:
所有智能指针都不允许进行隐式转换。
例:shared_ptr<double> pds;
double *p_reg = new double;
pds = shared_ptr<double>(p_reg);
//pds = p_reg;非法
shared_ptr<double> pshared (p_reg);//复制构造函数
// shared_ptr<double> pshared = p_reg;非法
(4)三种智能指针的区别
例:
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
auto_ptr<string> films[4]=
{
auto_ptr<string> (new string("first")),
auto_ptr<string> (new string("second")),
auto_ptr<string> (new string("third")),
auto_ptr<string> (new string("fourth"))
};
auto_ptr<string> pwin;
pwin = films[2];
for(int i = 0;i<4;i++)
{
cout<<*films[i]<<endl;
}
cout<<*pwin<<endl;
return 0;
}
说明:在运行*films输出时出错。原因:pwin = films[2]这句话是auto_ptr放弃了films[2]的所有权,则此时的films[2]是一个空指针,调用时会出错。
shared_ptr<string> films[4]=
{
shared_ptr<string> (new string("first")),
shared_ptr<string> (new string("second")),
shared_ptr<string> (new string("third")),
shared_ptr<string> (new string("fourth"))
};
shared_ptr<string> pwin;
pwin = films[2];
for(int i = 0;i<4;i++)
{
cout<<*films[i]<<endl;
}
cout<<*pwin<<endl;
说明:I、将auto_ptr换成shared_ptr,则输出成功。原因:pwin=films[2]指向同一个对象,shared_ptr中的引用计数从1增加到2.程序结束后,后声明的pwin首先调用析构函数,此时的引用计数减1。然后释放films[2],调用析构函数,此时引用计数减为0,,并释放以前的分配空间。
II、当使用unique_ptr时,因为它也是用的是所有权模型,所以也会崩溃。但是unique_ptr比auto_ptr更安全,因为若使用unique_ptr,则在pwin=films[2]阶段就会报错(编译阶段错误比潜在的程序崩溃更安全)。
(5)具体来说unique_ptr和shared_ptr指针的区别是:
unique_ptr(独占的智能指针):独占所指向的对象,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr,但可以通过函数返回给其他的unique_ptr,或者用std::move转移到其他的unique_ptr。
shared_ptr(共享的智能指针):允许多个指针指向同一个对象,内部维护一个计数器,无论何时拷贝一个shared_ptr,计数器都会递增,当指向该对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁该对象。其中use_count()
有多少个指针指向当前对象,即引用计数。
基本用法:
初始化 优先使用make_shared
I、通过构造函数、shared_ptr辅助函数、reset方法来初始化
shared_ptr<int> p1(new int(1));
shared_ptr p2 = p1;
shared_ptr<int> p3;
p3.reset(new int(1)); // 与赋值类似,reset会更新引用计数,p3指向一个新对象,p3原来指向的对象计数-1
if (p3)
{
cout << "p3 not null" << endl;
}
II、应该优先使用make_shared来构造智能指针,更高效
auto p1 = make_shared<int>(100); 相当于shared_ptr<int> sp1(new int(100));
// 不能将原始指针赋值给智能指针
shared_ptr<int> p = new int(1); // error
获取原始指针
shared_ptr<int> ptr(new int(1));
int* p = ptr.get(); // 返回ptr中保存的指针,要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了。
(6)unique_ptr指针中的悬挂指针问题
unique_ptr<string> pu1(new string("unique"));
unique_ptr<string> pu2;
pu2 = pu1;
说明:这种赋值是不合法的,在编译阶段就不通过。因为pu1是一个左值(左值是指表达式结束后依然存在的持久对象),当pu1的所有权已经转让给pu2之后,但它还存在,变成了一个悬挂的智能指针,如果后面使用它,则程序会崩溃。
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string("unique"));
说明:这种赋值是合法的。因为unique_ptr<string>(new string("unique"))是一个右值(右值是指表达式结束时就不再存在的临时对象)。该构造函数创建的临时对象在其所有权转让给pu3后就被销毁。