一、什么是RAII?
RAll (Resource Acquisition ls Initialization)是由c++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化,使用局部对象来管理资源的技术称为资源获取即初始化;这里的资源主要是指操作系统中有限的东西如内存(heap)、网络套接字,互斥量,文件句柄等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入。
\RAlI的原理
资源的使用一般经历三个步骤:
a.获取资源(创建对象)
b.使用资源
c.销毁资源(析构对象)
但是资源的销毁往往是程序员经常忘记的一个环节,所以程序界就想如何在程序员中让资源自动销毁呢?
解决问题的方案是:RAll,它充分的利用了C++语言局部对象自动销毁的特性来控制资源的生命周期。给一个简单的例子来看下局部对象的自动销毁的特性:
class Object{ private: int *ip public: Object(int val=0){ ip=new int(val); } ~Object(){ delete ip; } }; void fun () { Object obj; return; }
讯享网
从该示例可以看出,当我们在fun函数中声明一个局部对象的时候,会自动调用构造函数进行对象的初始化,当整个fun函数执行完成之后,自动调用析构函数来销毁对象,整个过程无需人工介入,由操作系统自动完成,于是自然联想到,当我们在使用资源的时候,在构造函数中进行初始化,在析构函数中进行销毁。
整个RAII过程总结为四个步骤:
a:设计一个类封装资源
b:在构造函数中初始化
c:在析构函数中进行销毁操作
d:使用时定义一个该类的对象
二、智能指针的引入
智能指针是比原始指针更智能的类,解决悬空(dangling)指针或多次删除被指向对象,以及资源泄露问题,通常用来确保指针的寿命和其指向对象的寿命一致。智能指针虽然很智能,但容易被误用,智能也是有代价的。\n\n
1.为什么要使用智能指针\n因为裸指针存在很多问题,主要是下面这些:
1.难以区分指向的是单个对象还是一个数组;
2.使用完指针之后无法判断是否应该销毁指针,因为无法判断指针是否“拥有”指向的对象;
3.在已经确定需要销毁指针的情况下,也无法确定是用delete关键字删除,还是有其他特殊的销毁机制,例如通过将指针传入某个特定的销毁函数来销毁指针;
4.即便已经确定了销毁指针的方法,由于1的原因,仍然无法确定到底是用delete(销毁单个对象)还是delete[] (销毁一个数组);
5.假设上述的问题都解决了,也很难保证在代码的所有路径中(分支结构,异常导致的跳转),有且仅有一次销毁指针操作;任何一条路径遗漏都可能导致内存泄露,而销毁多次则会导致未定义行为
6.理论上没有方法来分辨一个指针是否处于悬挂状态。
三、auto_ptr
C++11提供了四种智能指针:auto_ptr; unique_ptr; shared_ptr; wek_ptr;
四、模拟实现auto_ptr
对于auto_ptr类,它的类成员除了一个指针,还有一个拥有权成员,它的作用时记录指针是否对所指之物有拥有权。
讯享网template<typename _Ty> class my_auto_ptr{ private: bool _Owns; _Ty* _ptr; public: my_auto_ptr(_Ty* p = nullptr) :_Owns(p != nullptr), _ptr(p) { } ~my_auto_ptr() { if (_Owns) { delete _ptr; } _Owns = false; _ptr = nullptr; } };
使用一个类来测试一下
class Object { private: int num; public: Object(int x = 0) : num(x) { cout << "Create Object: " << this << endl; } ~Object() { cout << "Destroy Object: " << this << endl; } }; int main(void) { my_auto_ptr<Object> obj(new Object(10)); return 0; }
运行结果
构造
析构
图示如下
1.(*)和(->)重载

为了方便调用智能指针指向对象的方法,可以对其进行重载:
比如Object对象里有其他方法
讯享网int& value(){ return num; } const int & value() const{ return num; }
运算符重载:
对于解引用的重载,利用get()得到_Ptr;对其解引用得到_Ptr指向的对象,以引用返回。
对于指向符的重载,直接返回get()即可。
_Ty* get()const{ return _Ptr; } _Ty& operator*()const { return *(get()); } _Ty* operator->()const { return get(); }
使用示例:
讯享网int maina() { my_auto_ptr<Object> obj(new Object(10)); cout<<obj->value()<<endl; cout<<(*obj).value()<<endl; return 0; }
2.重置指向(reset)和释放函数(release)
重置智能指针指向,意思就是不指向当前对象,指向另外一个对象。
void reset(_Ty* p=nullptr) { if(_Owns) { delete _Ptr; } _Ptr=p; }
释放即失去当前对象的拥有权,可以利用返回值方式让其他指针指向当前对象。
讯享网_Ty* release() const { _Ty* tmp = nullptr; if (_Owns) { ((my_auto_ptr*)this)->_Owns = false; tmp = _Ptr; ((my_auto_ptr*)this)->_Ptr = nullptr; } return tmp; }
3.拷贝构造带来的问题
一:重复析构即两个指针指向同一块内存
如果拷贝构造是浅拷贝就会出现下面这种情况
my_auto_ptr(const my_auto_ptr& op) : _Owns(op._Owns) { if (_Owns) { _Ptr = op._Ptr; } } int main(void) { my_auto_ptr<Object> obja(new Object(10)); my_auto_ptr<Object> objb(obja); return 0; }
运行结构程序结束时,会对指向Object对象析构两次,导致堆破坏。
二.失去拥有权引发程序崩溃
在拷贝构造中,让拷贝的智能指针释放资源转移给待拷贝的智能指针
讯享网my_auto_ptr(const my_auto_ptr& op) : _Owns(op._Owns), _Ptr(op.release()) { }
调用fun()函数时将object作为参数传递过去:
void fun(my_auto_ptr<Object> op) { cout << op->value() << endl; } int main(void) { my_auto_ptr<Object> obja(new Object(10)); fun(obja); cout << obja->value() << endl; return 0; }
传递给形参时,需要调用拷贝构造函数,那么就会将资源转移给形参op,在fun()函数结束时形参op会自动析构掉。
如图:
在进入fun()函数时,对象拥有权已经在形参手里,原来的obja指针已经不拥有Object对象了

在fun()函数结束时,形参op也自动析构,也析构了Object对象。

那么再想通过原来的obja指针来访问Object对象,就会引发异常:读取访问权限冲突。

赋值运算符重载和拷贝构造一样,会引发这些问题。
4.指向一组对象引发的错误
于在auto_ptr的析构函数设计的是 释放当前指针指向的内容,即delete _Ptr; 这样在初始化指向一组对象时,析构函数只析构了一个对象,引发内存泄漏。
总结:
auto_ptr主要有三大问题:
1.复制和赋值会改变资源的所有权
2.在STL容器中使用auto_ptr有极大风险,因为容器中元素必须支持可复制和可赋值。
3.不支持对象数组操作

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/61872.html