智能指针之 Auto

    xiaoxiao2021-04-01  30

    在介绍智能指针之前我们先看以下代码:

    void FunTest() { int *p = new int[10]; FILE* pFile = fopen( "1.txt", "w" ); if (pFile == NULL) { return; } // DoSomethint(); if (p != NULL) { delete[] p; p = NULL; } }

    在该代码中,我们注意了对指针的释放却忽略了对文件指针的关闭。C++中的动态内存需要用户自己来维护,动态开辟的空间,在出函数作用域或者程序正常退出前必须释放掉,否则会造成内存泄露,有时我们已经非常谨慎了,然防不胜防。

    智能指针(RAII)的存在一开始就是为了解决资源分配即初始化,其解决方案是定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。

    所以有了第一代Auto_Ptr

    template<class T> class AutoPtr { public: AutoPtr(T* ptr = NULL) :_ptr(ptr) {} AutoPtr(AutoPtr<T>& s) :_ptr(s._ptr) { s._ptr = NULL; //经过赋值拷贝,原本的智能指针s中_ptr 已经指向空 } AutoPtr<T>& operator=(AutoPtr<T>& s) { if (this != &s) { if (_ptr) { delete _ptr; } _ptr = s._ptr; s._ptr = NULL; //经过赋值运算符后同样被置空 } return *this; } T* operator->()const { return _ptr; } T& operator*()const { return *_ptr; } ~AutoPtr() { if (_ptr) { delete _ptr; } } private: T* _ptr; }; void Funtest1(AutoPtr<char> p) {} void Funtest2(AutoPtr<char>& p) {} int main() { AutoPtr<char> p1(new char); AutoPtr<char> p2(p1); //AutoPtr<char> p4(p3); 出错,不能通过编译,原因在拷贝构造函数中更改了具有常性的 p3 //Funtest1(p2); 同理,以传值形式作为函数形参,也要经过拷贝构造函数形成临时变量p,同理赋值崩溃 Funtest2(p2); //以引用的方式传递没有问题 *p2 = '1'; return 0; }

    由此可见一代Auto_Ptr的缺点都有以下几点: 1:在Auto_Ptr中经过资源转移后,p1不能再访问空间 2:以值传递的形式作为函数参数后,一旦调用函数,该智能指针在原函数中也不能访问空间 3:const对象不能构造同类型对象

    针对第一代Auto_Ptr的问题,于是乎有了第二代Auto_Ptr的产生实际上我们是使用一个bool 类型的 数据成员来表明该智能指针是否具有被析构的能力

    template<class T> class AutoPtr { public: AutoPtr(T* ptr = NULL) :_ptr(ptr) , type(false) { if (_ptr) type = true; } AutoPtr(const AutoPtr<T>& s) :_ptr(s._ptr) , type(s.type) { s.type = false; } //拷贝构造函数中,将s对象具有析构的能力 type赋给新对象,而s.type赋值为false,说明不具有析构能力 AutoPtr<T>& operator=(const AutoPtr<T>& s) { if (this != &s) { if (_ptr && type) { delete _ptr; } _ptr = s._ptr; type = s.type; s.type = false; } return *this; } T& operator*()const { return *_ptr; } //析构函数中,我们将 type 为true的智能指针进行析构,从而避开了对同一空间释放多次的问题 ~AutoPtr() { if (_ptr && type) { delete _ptr; _ptr = NULL; type = false; } } private: T* _ptr; mutable bool type; //在成员函数体内只改变了type的值,针对于const对象,我们加关键字mutable可以更改type的值 }; void Funtest1(AutoPtr<char> p) {} int main() { AutoPtr<char> p1(new char); void Funtest1(p1); AutoPtr<char> p2(p1); AutoPtr<char> p3(new char); AutoPtr<char> p4(p3); Funtest1(p1); p1 = p3; *p3 ='1'; return 0; }

    经过验证,我们确定基于第一代的修改我们成功的引入了第二代,但是我们引入了一个野指针的问题,一旦对于野指针进行操作,很容易造成问题,

    当我们用智能指针管理一个无名的智能指针对象时,编译器会自动优化,调用系统的构造函数,如果我们想调用我们自己写的构造函数,我们推导出智能指针的第三个版本

    template<class T> class autoptrref { public: autoptrref(T* ptr = NULL) :_ptr(ptr) {} T* _ptr; }; template<class T> class autoptr { public: //构造函数 autoptr(T* ptr = NULL) :_ptr(ptr) {} //拷贝构造函数 autoptr(autoptr<T>& ap) :_ptr(ap._ptr) { ap._ptr = NULL;//拷贝完后将ap._ptr置NULL } //赋值运算符的重载 autoptr<int>& operator=(autoptr<T>& ap) { if (this != &ap) { if (_ptr != NULL) { delete _ptr; } _ptr = ap._ptr; ap._ptr = NULL;//赋值完后将ap._ptr置NULL } return *this; } //解决办法 //重载autoptrref() operator autoptrref<T>()//将autoptr<T>转化为autoptrref<T> { autoptrref<T> temp(_ptr); _ptr = NULL; return temp; } //再重载一个拷贝构造函数 autoptr(const autoptrref<T>& apr) :_ptr(apr._ptr) {} ~autoptr()//析构函数 { if (_ptr != NULL) { delete _ptr; } } //*的重载 T& operator*()const { return *_ptr; } //得到原生态指针 T* Get()const { return _ptr; } private: T* _ptr; }; void funtest() { autoptr<int> ap(autoptrref<int>(autoptr<int>(new int))); //用无名的智能指针拷贝构造智能指针ap,则不会调用我们编写好得拷贝构造函数,而是调用系统自动合成个的拷贝构造函数 } //原因是编译器的自动优化,解决办法:重新增加一个类autoptrref,将无名对象的类型转化为autoptrref int main() { funtest(); return 0; }
    转载请注明原文地址: https://ju.6miu.com/read-665662.html

    最新回复(0)