智能指针
1.为什么需要智能指针
在异常的时候我们说过这里的问题,下面这种写法抛异常了会造成内存泄漏的问题。
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void Func()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
int* p1 = new int[10];
int* p2 = nullptr;
cout << div() << endl;
delete[] p1;
delete[] p2;
}
int main()
{
try
{
Func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
前面我们的处理方式是把异常捕捉一下然后重新抛出。
void Func()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
int* p1 = new int[10];
int* p2 = new int[10];
try {
cout << div() << endl;
}
catch (...)
{
delete[] p1;
delete[] p2;
throw;
}
delete[] p1;
delete[] p2;
}
但是这样的处理方式并不好。
第一代码很丑。
第二要注意到new的时候也会抛除异常。
p1这里抛异常倒是没事,如果抛异常直接跳转到main的catch进行捕捉下面都不会影响。但是p2抛了异常呢?p1已经成功申请空间了,如果不处理又会造成内存泄漏!
void Func()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
int* p1 = new int[10];
int* p2 = nullptr;
try {
p2 = new int[10];
try {
cout << div() << endl;
}
catch (...)
{
delete[] p1;
delete[] p2;
throw;
}
}
catch(...)
{
delete[] p1;
//...
}
delete[] p1;
delete[] p2;
}
如果又有p3,p4呢? 这里最本质的问题就是new本身就会抛异常。
这里真正的解决方式就是智能指针!
我们看看智能指针内部是怎么实现的。
template<class T>
class Smartptr
{
public:
Smartptr(T* ptr)
:_ptr(ptr)
{
}
~Smartptr()
{
//下面是[]这里暂时写成这个样子
delete[] _ptr;
cout << _ptr << endl;
}
private:
T* _ptr;
};
void Func()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
int* p1 = new int[10];
Smartptr<int> sp1(p1);
int* p2 = new int[10];
Smartptr<int> sp2(p2);
cout << div() << endl;
//不需要主动释放,无论是谁抛异常都能释放
//delete[] p1;
//delete[] p2;
}
抛异常栈帧会正常销毁,栈帧销毁这些对象就会出作用域,自定义类型出作用域就会调用析构函数,就会释放资源。
下面这里是把一个new出来的资源交给智能指针的对象,出了作用域就会自动把资源释放。
void Func()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
int* p1 = new int[10];
Smartptr<int> sp1(p1);
int* p2 = new int[10];
Smartptr<int> sp2(p2);
cout << div() << endl;
//不需要主动释放,无论是谁抛异常都能释放
//delete[] p1;
//delete[] p2;
}
有了智能指针抛异常带来的内存泄漏的问题就得到极大解决。
这里也可以直接把new出来的资源交给智能指针
void Func()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
//int* p1 = new int[10];
//Smartptr<int> sp1(p1);
//int* p2 = new int[10];
//Smartptr<int> sp2(p2);
Smartptr<int> sp1(new int[10]);
Smartptr<int> sp2(new int[10]);
cout << div() << endl;
}
智能指针,也想解引用一下有什么方式可以处理一下呢?
可以重载一下。
template<class T>
class Smartptr
{
public:
//RAII
//构造---保存资源
Smartptr(T* ptr)
:_ptr(ptr)
{
}
//析构---释放资源
~Smartptr()
{
//下面是[]这里暂时写成这个样子
delete[] _ptr;
cout << _ptr << endl;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
};
下面就可以像普通指针一样去解引用等
void Func()
{
//直接把new出来的资源交给智能指针
Smartptr<int> sp1(new int[10]);
Smartptr<int> sp2(new int[10]);
*sp1 = 10;
sp1[0]--;
cout << *sp1 << endl;
cout << div() << endl;
}
2.智能指针原理
智能指针的原理:
- RAII特性
- 重载operator*和opertaor->,具有像指针一样的行为。
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效(资源生命周期与对象声明周期进行绑定),最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效
3.智能指针的使用以及问题
智能指针的使用我们已经见识到了。那它有没有什么问题呢?
就以我们刚才写的智能指针为例。
template<class T>
class Smartptr
{
public:
//RAII
//保存资源
Smartptr(T* ptr)
:_ptr(ptr)
{
}
//释放资源
~Smartptr()
{
delete _ptr;
cout << _ptr << endl;
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T& operator[](size_t pos)
{
return _ptr[pos];
}
private:
T* _ptr;
};
int main()
{
Smartptr<int> sp1(new int);
Smartptr<int> sp2(sp1);
return 0;
}
运行奔溃了,原因是什么?