文章目录
- 存在内存泄漏
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void Func()
{
int* p1 = new int;//p1抛异常,直接跳catch,没有问题
cout << div() << endl; // div抛异常,调到catch,p1无法释放,资源泄露
delete p1;
}
int main()
{
try
{
Func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
new空间也有可能会抛出异常,对于p1如果抛出异常:没有问题,可以不管,直接到最外面去了。
而如果用户输入的除数为0,那么div函数就会抛出异常,跳到主函数的catch块中执行,但是别忘了,此时Func()中的申请的内存资源还没有释放!
对于这种情况,我们可以进行异常的重新捕获
- 异常的重新捕获
在func函数中对div函数中的抛出的异常重新捕获,将申请的内存进行释放,然后在将异常重新捕获:
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void Func()
{
int* p1 = new int;//p1抛异常,直接跳catch,没有问题
try
{
cout << div() << endl;
}
catch (...)
{
delete p1;
throw;
}
delete p1;
}
int main()
{
try
{
Func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
但是如果申请的不是上面的一块空间,而是更多呢,还有p2,p3…?这时候就麻烦了,需要套很多,所以这时候智能指针就登场了,可以解决这个问题。
二、智能指针的使用与原理
- 智能指针的使用
template<class T> class SmartPtr { public: SmartPtr(T* ptr) :_ptr(ptr) {} ~SmartPtr() { cout << "delete: " << _ptr << endl; delete _ptr; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T& operator[](size_t pos) { return _ptr[pos]; } private: T* _ptr; }; int div() { int a, b; cin >> a >> b; if (b == 0) throw invalid_argument("除0错误"); return a / b; } void func() { SmartPtr<int> sp(new int); int* p2 = new int; SmartPtr<int> sp2(p2); cout << div() << endl; } int main() { try { func(); } catch (exception& e) { cout << e.what() << endl; } return 0; }
上面代码将申请到的内存交给了SmartPtr对象管理:
-
在构造SmartPtr对象时,自动调用构造函数,将传入的需要管理的内存保存起来;
在析构SmartPtr对象时,自动调用析构函数,将管理的内存空间进行释放
SmartPtr还可以与普通指针一样使用,需对*和->以及[]进行运算符重载
通过SmartPtr对象,无论程序是正常执行结束,还是因为某些中途原因进行返回,或者抛出异常等开始所面临的困境,只要SmartPtr对象的生命周期结束就会自动调用对应的析构函数,不会造成内存泄漏,完成资源释放。
- 智能指针的原理
RAII: RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保存有效,最后在对象析构时释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象,好处:
不需要显式地释放资源
采用这种方式,对象所需的资源在其生命周期内始终保持有效
- 智能指针对象拷贝问题
对于我们上面所实现的SmartPtr,如果用一个SmartPtr对象来拷贝构造另一个SmartPtr对象,或者一个SmartPtr对象赋值给另一个SmartPtr对象,最终结果会导致程序崩溃:
void test()
{
SmartPtr<int> sp1(new int);
SmartPtr<int> sp2(sp1);//拷贝构造
SmartPtr<int> sp3(new int);
SmartPtr<int> sp4 = sp3;//赋值
}
编译器默认生成的拷贝构造函数对内置类型完成浅拷贝(值拷贝),sp1拷贝给sp2后,两个管理同一块空间,当sp1和sp2析构就会让这块内存空间释放两次。同理,编译器默认生成的卡搜被赋值函数对内置类型完成浅拷贝,把sp4赋值给sp3后,sp4与sp3都是管理原来sp3的空间,会析构两次,同时,原先sp4管理的内存没有释放。
单纯的浅拷贝会导致空间多次释放,因为根据智能指针解决卡搜被问题方式不同,所以有很多版本的智能指针