讲这个之前,先来看一个例子:
void Test1 () { int* p1 = new int(2); //... try { DoSomeThing(); } catch(...) { delete p1 ; throw; } //... delete p1 ; }
这个例子,是通过C++异常处理机制,来管理动态开辟出来的内存,这是可以做到的。但是以后在开发软件产品时,需要开辟动态内存,你都这样处理,就显得非常繁琐,代码量也会加大,有时候逻辑会理不清楚,于是有人提出能不能将“动态内存拥有权”这个问题的复杂性,从软件产品本身的复杂性中分离出来,由专门的人或团队来负责实现,实际上是非常有利于软件的模块化和复用性的。毕竟,从本质上来看,动态内存的拥有权和一款软件产品本身所要解决的目标问题在相当大程度上是正交的,将其分解开来,分而治之从软件工程学的角度来看实在是个不错的选择。也显得非常繁琐,那C++为了处理这个问题,提出了一个叫RAII(Resource Acquisition Is Initialization)。
RAII:资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。这个类具体到下面的智能指针。
智能指针:所谓智能指针就是智能\自动化管理动态开辟内存的释放。
智能指针设计思想:首先提供一个类,然后用这个类去封装原生指针,并且封装类会提供客户端代码通常会施加在原生指针上的绝大多数操作接口。这样,在保证跟原生指针相近的操作体验的同时,简化了动态内存的管理负担。
在C++11之前,标准库中,只有一个auto_ptr,下面是模拟实现auto_ptr旧版本.
/*旧版本*/ /*实现原理:通过拥有者的改变来最后确定是否析构对象*/ #include <iostream> using namespace std; template<class T> class AutoPtr { public: AutoPtr(T* ptr) :_ptr(ptr) { _owner = true; } AutoPtr(AutoPtr<T>& ap) :_ptr(ap._ptr),_owner(ap._owner) { ap._owner = false; } AutoPtr<T>& operator=(AutoPtr<T>& ap) { if(this != &ap) { if(_owner) { delete _ptr; } _ptr = ap._ptr; _owner = ap._owner; if(ap._owner) { ap._owner = false; } } return *this; } ~AutoPtr() { if(_owner) { delete _ptr; } } public: T& operator*() { return *_str; } T* operator ->() { return _str; } private: T* _ptr; bool _owner; }; void Test1() { AutoPtr<int> ap1(new int(1)); AutoPtr<int> ap2(ap1); AutoPtr<int> ap3(new int(3)); ap3 = ap2; } int main() { Test1(); return 0; }
显示结果:
这样看结果是对的,但是看下面这个Test2()
void Test2() { AutoPtr<int> ap1(new int(1)); AutoPtr<int> ap2(ap1); AutoPtr<int> ap3(new int(3)); AutoPtr<int> ap4(ap3); ap4 = ap1; }
显示结果:
看上面就出现问题了,你把ap1 赋给 ap4 ,但是ap4的_owner 是 false ,这就是有问题的。到最后,ap4只是指向共有的那块空间而已,没有达到真正的管理,再看Test3()
void Test3() { AutoPtr<int> ap1(new int(1)); if(1) { AutoPtr<int> ap2(ap1); } *ap1 = 10; }
显示结果:
程序直接崩溃了,因为产生了野指针的访问,访问的那一块空间已经释放了,所以就会崩溃了,正是由于旧版本有这诸多的问题,C++改进了一下他,使他变得很“强大”,下来看模拟实现:
/*新版本*/ /*实现原理:管理权的转交*/ #include <iostream> using namespace std; template <class T> class AutoPtr { public: AutoPtr(T* str) :_str(str) {} AutoPtr(AutoPtr<T>& ap) :_str(ap._str) { ap._str = NULL; } AutoPtr<T>& operator=(AutoPtr<T>& ap) { if(this != &ap) { delete _str; _str = ap._str; ap._str = NULL; } return *this; } ~AutoPtr() { if(_str) delete _str; } public: T& operator*() { return *_str; } T* operator ->() { return _str; } private: T* _str; }; struct A { int _a; }; void Test4() { AutoPtr<int>ap(new int(1)); AutoPtr<int>ap2(ap); AutoPtr<int>ap3(new int(2)); AutoPtr<int>ap4(ap3); ap4 = ap; } int main() { Test4(); return 0; }
显示结果:
当然这个结果是正确的,但是你要这个AutoPtr又有什么用,你用对象之间拷贝构造新对象和对象之间相互赋值时,就是为了让他们共同管理,但是现在,这就有点low了,既没有达到那种共同管理,也在拷贝构造对象和相互赋值时,不清不楚,那么他就相当low。一些C++技术大牛,在开源库函数boost库(可移植的函数库)中引出了新的智能指针,受到广大编程爱好者的一致好评。于是在C++11标准引出了新的智能指针,unique_ptr,shared_ptr,weak_ptr。
模拟实现unique_ptr:
#include <iostream> using namespace std; template <class T> class UniquePtr { public: UniquePtr(T* ptr) :_ptr(ptr) {} ~UniquePtr() { if(_ptr != NULL) { delete _ptr; } } protected: UniquePtr(UniquePtr<T>& up); UniquePtr<T> operator=(UniquePtr<T>& up); public: T* operator*() { return *_ptr; } T& operator->() { return _ptr; } private: T* _ptr; }; void Test1() { UniquePtr<int> up1(new int(1)); UniquePtr<int> up2(new int(2)); } int main() { Test1(); return 0; }
显示结果:
没毛病,下来看test2():
void test2() { UniquePtr<int> up1(new int(1)); UniquePtr<int> up2(new int(2)); up2 = up1; UniquePtr<int> up3(up1); }
显示结果:
在编译的时候出现了问题,这是为什么呢?
protected: UniquePtr(UniquePtr<T>& up); UniquePtr<T> operator=(UniquePtr<T>& up);
由于在类中只声名拷贝构造函数和赋值运算符重载,不实现,防止调用默认的拷贝构造函数和赋值运算符重载造成浅拷贝的问题,加上保护限制,防止在被继承时,对其进行重写。避免了auto_ptr出现的问题,我unique_ptr就是不让被复制和赋值,他是一种防拷贝(你就是霸道)的实现,没有像auto_ptr那样转移管理权,直接就是我的就是我的,你们其他人边去,但是我们也想要实现向原生指针那样共同管理,那就可以shared_ptr,模拟实现shared_ptr:
#include <iostream> using namespace std; template <class T> class SharedPtr { public: SharedPtr(T* ptr) :_ptr(ptr),_pcount(new int(1)) { cout<<"SharedPtr(T* ptr)"<<endl; } ~SharedPtr() { cout<<"~SharedPtr()"<<endl; _Release(); } SharedPtr(SharedPtr<T>& sp) :_ptr(sp._ptr),_pcount(sp._pcount) { cout<<"SharedPtr(SharedPtr<T>& sp)"<<endl; ++(*_pcount); } SharedPtr<T>& operator= (SharedPtr<T> sp)//传值,赋值前调用拷贝构造函数 { cout<<"SharedPtr<T>& operator="<<endl; swap(_ptr, sp._ptr); swap(_pcount, sp._pcount); return *this; } public: /*接口函数*/ T& operator*()//提供*接口 { return *_ptr; } T* operator->()//提供->接口 { return _ptr; } public: int UseCount() { return *_pcount; } T* GetPtr() { return _ptr; } protected: void _Release() { if(--(*_pcount) == 0) { delete _ptr; delete _pcount; } } private: T* _ptr; int* _pcount; }; int main() { SharedPtr<int> sp(new int(1)); SharedPtr<int> sp1(sp); SharedPtr<int> sp2(new int(2)); SharedPtr<int> sp3(new int(3)); sp3 = sp2; int count = sp2.UseCount(); return 0; }
显示结果:
第一次构造函数:
SharedPtr<int> sp(new int(1));
第一次拷贝构造函数:
SharedPtr<int> sp1(sp);
第二次,第三次构造函数:
SharedPtr<int> sp2(new int(2)); SharedPtr<int> sp3(new int(3));
第二次拷贝构造函数,第一次赋值:
sp3 = sp2;
因为赋值时,采用传值方式,所以调用拷贝构造函数。
第一次析构函数,析构临时拷贝构造出来的临时对象。
第二次析构函数,析构sp3,sp2(是共同管理一块内存的)。
第三次析构函数,析构sp,sp1(是共同管理一块内存的)。
完美...
是不是新的智能指针完美的解决auto_ptr的问题呢,对,是的。所以在以后需要使用智能指针的时候,不要用auto_ptr,因为他们完美的诠释原生指针,当不需要复制,赋值时,应该首选unique_ptr,需要复制和赋值就需要选择shared_ptr.
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。