1.为什么需要智能指针
分析一下下面这段程序有没有什么内存方面的问题?前面在异常的博客中,我们分析了下图一的代码Func函数中如果 div ()函数抛异常则程序会直接跳到主函数的catch捕获程序部分,然后接着主函数catch捕获程序部分往后执行代码,那么Func函数中 new的空间没有执行后面的delete释放空间代码会造成内存泄漏。因此在异常的博客中我们的处理办法是在Func函数中增加try catch捕获拦截异常,将 delete释放空间操作处理完之后再把异常抛出去,如下图二所示。在异常的博客中new开辟一次空间上图二所示的方式是可以的,但上图二所示的代码两次new开辟空间,如果第二次new开辟空间开辟失败抛了异常,例如上面代码int* p2 = new int开辟失败报异常会直接跳到主函数的catch捕获程序部分,然后接着主函数catch捕获程序部分往后执行代码,那么Func函数中代码int* p2 = new int往后的代码都没有运行,因此第一次new开辟空间后也没有delete释放空间,会造成内存泄漏。
那么如何解决上面的问题呢?c++提出了智能指针的解决方案。
2.智能指针的使用及原理
2.1.RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:不需要显式地释放资源。采用这种方式,对象所需的资源在其生命期内始终保持有效。注:RAII技术是智能指针实现的指导思想。如下图所示,我们使用智能指针类来解决本博客第一节的问题,这样即使Func函数中第二次new开辟空间失败即SmartPtr<int> sp2(new int)中new int开辟空间失败抛异常, 程序会直接跳到主函数的catch捕获程序部分,然后接着主函数catch捕获程序部分往后执行代码,此时Func函数栈帧已经销毁,sp1智能指针通过系统自动调用智能指针类的析构函数将sp1智能指针指向的空间进行了释放,解决了内存泄漏的问题。// 使用RAII思想设计的SmartPtr类 template<class T> class SmartPtr { public: SmartPtr(T* ptr = nullptr) : _ptr(ptr) {} ~SmartPtr() { if (_ptr) delete _ptr; } private: T* _ptr; }; int div() { int a, b; cin >> a >> b; if (b == 0) throw invalid_argument("除0错误"); return a / b; } void Func() { //使用方法一 int* p1 = new int; SmartPtr<int> sp1(p1); //使用方法二 SmartPtr<int> sp2(new int); cout << div() << endl; } int main() { try { Func(); } catch (const exception& e) { cout << e.what() << endl; } return 0; }
2.2.智能指针的原理
上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此AutoPtr模板类中还得需要将operator*函数和operator->函数重载一下,才可让其像指针一样去使用。template<class T> class SmartPtr { public: SmartPtr(T* ptr = nullptr) : _ptr(ptr) {} ~SmartPtr() { if (_ptr) delete _ptr; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; }; struct Date { int _year; int _month; int _day; }; int main() { SmartPtr<int> sp1(new int); *sp1 = 10; cout << *sp1 << endl; SmartPtr<Date> sparray(new Date); // 需要注意的是这里应该是sparray.operator->()->_year = 2018; // 本来应该是sparray->->_year这里语法上为了可读性,省略了一个-> sparray->_year = 2023; sparray->_month = 1; sparray->_day = 1; }
总结一下智能指针的原理:1. RAII特性.2. 重载operator*和opertaor->,具有像指针一样的行为。