什么是RAll?
使用局部对象****来管理资源的技术称之为资源获取即初始化。
这里的资源主要是指操作系统中有限的东西,如:内存,网络套接字,互斥量,文件句柄等等,局部对象实质存储在栈的对象。它的生命周期是由操作系统管理的。无需人工的介入。
1. RAll的原理:
1.1 资源的获取一般分为三个阶段:
1.创建资源 (创建对象)
2.使用资源
3.获取资源 (调用析构函数)
1.2 资源的销毁往往是程序员经常忘掉的环节,所以程序界就想,如何才能让资源自动销毁?
答: 解决的方案就是RAll,它充分利用了C++语言局部对象,自动销毁的特性来控制资源的生命周期。
局部对象就是在函数中的一个对象,调动函数,对象被产生,函数结束,对象被销毁。局部的函数的创建和销毁分别调用了构造函数和析构函数。
裸指针
在研究智能指针之前,我们先探讨一下裸指针的概念。
什么是裸指针?
如下:
int main()
{
int *ip;//未初始化,野指针
int *ip =NULL; //裸指针
Object *op = NULL; //裸指针
}
通俗的讲,凡是带*并且初始化的都是裸指针。
裸指针带来的问题:
1.无法判断裸指针指向的是一个对象还是一组对象。
2.
class Object
{
private:
int value;
public:
Object(int x = 0) :value(x) { cout << "create Object" << this << endl;}
~Object() { cout << "destory Obejct" << this << endl; }
int& value(){}
};
添加一个fun()
void fun(Object* op) //传给op的&obj和arobj ,我们无法判断传给op的是一个对象还是一组对象。
{
}
int main()
{
Object obj(10);
Object arobj[10];
fun(&obj); //无法判断,第一次传给op的是一个对象
fun(aobj); //第二次传的是一组对象。
}
无法判断,第一次传给op的是一个对象,第二次传的是一组对象。
再添加一个Destory()
void Destory(Object* op)
{
delete op;
delete[]op;
}
int main()
{
Object *op = new Obejct(10);
Object *arop = new Object[10];
Destory(op); //析构的时候也根本没办法不知道,是析构一组对象,还是一个对象。
Destory(arop);
}
析构的时候也根本没办法不知道,是析构一组对象,还是一个对象。所以会将delete op和delete[]op通通执行。
往类中添加一个方法。
int& value(){}
};
int main()
{
Object *op =NULL;
op= new Object(10);
op->value();
Destory(op);
//op此时是失效指针,空悬指针
// 也无法判断裸指针是否被释放。
}
因此可以总结关于裸指针的以下几点问题:
问题总结:
1.难以区分指向的是单个对象还是一组对象。
2.使用完指针之后,无法判断是否应该销毁指针,因此也就无法判断指针是否“拥有”指向的对象。
3.在已经确定需要销毁指针的情况下,也无法确定是用delete关键字删除,还是其他特殊的销毁机制,例如通过将指针传入某个特定的销毁函数来销毁指针。
4.即便已经确定了销毁的方法,,由于1的原因,无法确认用delete还是delete[]。
5.假设上述问题都解决了,也很难保证在代码的所有路径中(分支结构,异常导致的跳转),有且仅有一次销毁指针的操作,任何一条路径一楼都会导致内存泄漏。
6.理论上没有办法来分辨一个指针是否处于悬挂状态。
所以要使用智能指针。
智能指针:
c11:
unique_ptr;
share_ptr;
weak_ptr;
被抛弃:
auto_ptr
1. 为什么要抛弃auto_ptr
首先我们先将auto_ptr实现一下,我们就可以从中的找到不使用它 的原因。
Object类可以作为类模板
class Object
{
int value;
public:
Object(int x = 0) :value(x) { cout << "create Object" << endl; }
~Object() { cout << "Destory Object" << endl; }
};
template<class _Ty>
class my_auto_ptr
{
private:
bool _Owns; //拥有权
_Ty* _Ptr; //指针
public:
//构造函数
my_auto_ptr(_Ty* p = NULL) :Owns(p != NULL), _Ptr(p) {}
//析构函数
~my_auto_ptr()
{
if (_Owns)
{
delete _Ptr;
}
_Owns = false;
_Ptr = NULL;
}
};
测试:
int main()
{
//_Ty = Object
my_auto_ptr<Object> obj(new Object(10));
//1.new完成返回的是一个对象的地址
} //2.调用my_auto_ptr类的构造函数创建obj对象,
//3.p指向Object不具名对象的地址。
//4. p->value =10 _Ptr(p) _Ptr->value = 10;
return 0;
}
obj 是智能指针对象。
obj对象中的 _ptr指针的类型是Object。
因此_ptr指针指向的Object类内存空间。
关于: my_auto_ptr<Object> obj(new Object(10));
的实现过程。
第一步:new Object(10)
new Object(10)调用构造函数,创建对象,申请堆空间,对value进行初始化
1.new完成返回的是一个对象的地址,
- 调用my_auto_ptr类的构造函数创建obj对象,
- p指向Object不具名对象的地址。
- p->value =10 _Ptr§ _Ptr->value = 10;
最终内存图:
接下来当我们在Object类中定义两个方法:
int& value()
{
return value;
}
const int& value()const { return value; }
在类外使用fun()函数来访问value()
void fun()
{
my_auto_Ptr<Object> obj(new Object(10));
//obj->_Ptr->value()
cout << obj->value() << endl;
//*(obj._Ptr).value()
cout << (*obj).value() << endl;
}
我们想要直接通过对象使用"->“和” * "访问成员函数就需要对这两个运算符进行重载:
2. 重载->和*
如下:
在my_auto_ptr中写入
_Ty *get()const{return _Ptr}
_Ty &operator *()const
{
return *(get());
}
_Ty *operator->()const
{
return get();
}
说白了就是转换成直接使用Object类的指针访问Object类中的value()函数。
对于内置类型:如:(遵循的是C的概念)
int *ip = new int(10);
*ip = 100;
- 解释为解引用。
而class 是遵循C++的概念。
C++完全兼容C。
智能指针不处理带const的指针。
3. 重置指向(reset )
就是my_auto_ptr对象obj中的_ptr指针指向另外一个对象。
void reset(_Ty *p=NULL)
{
if(_Owns)
{
delete _Ptr;
}
_Ptr = p;
}
4. 释放函数(release)
释放函数就是释放对当前对象的拥有权。
4.1 出现的问题(1):多次析构同一个堆空间导致程序崩溃。
_Ty *release()const
{
_Ty*tmp = NULL;
if(_Owns)
{
//强转成普通类型的指针
((my_auto_ptr*)this)->_Owns = false;
tmp = _Ptr;
((my_auto_ptr*)this)->_Ptr = NULL;
}
return tmp;
}
将一个对象赋值给另外一个对象
//加入
//this指针只想的是obja ,p是obj的引用
my_auto_ptr(const _Ty &p):_Owns(p._Owns)
{
if(_Owns)
{
_Ptr = p._Ptr;
}
}
int main()
{
my_auto_ptr<Object> obj(new Object(10));
my_auto_ptr<Object>obja(obj);
//两个对象保存同一个堆空间,析构时导致内存泄漏。
}
两个对象保存同一个堆空间,析构时导致内存泄漏。
使用release将obj的资源转给obja
重写构造函数。
//拷贝构造函数必须以引用的方式传值
my_auto_ptr(const _Ty &p):_Owns(p._Owns),_Ptr(p,release())
{
if(_Owns)
{
_Ptr = p._Ptr;
}
}
void fun(my_auto_ptr<Object> obja)
{
int x = obja->value(); //可以正常执行。
}
int main()
{
my_auto_ptr<Object>obj(new Object(10));
fun(obj);
//此时由于调用拷贝构造,并使obj失去了对对象的拥有权,
int a = obj->value(); //obj自然也无法调动运算符,程序崩溃。
}
传参:调用拷贝构造函数,此时会创建一个临时对象,给其_Owns传入obj的_Owns,使用使用release(),让obj失去对对象的拥有(_Owns = false),让tmp指向obj中_Ptr,return tmp;临时对象中的_Ptr指向tmp,将临时对象的地址传给obja。传参结束。析构临时对象,fun()函数结束,执行析构函数,析构obja。
4.2 出现的问题(2):失去拥有权导致程序崩溃
此时obj已经失去了对对象的拥有权,因此也就无法执行obj->value()。
图示:
赋值运算符重载还是和上面拷贝构造函数出现的问题一样,要么导致内存泄漏,要么obj的将失去对象的拥有权。在接下来执行其他程序就会出现问题。
4.3 问题(3):不清楚生成的是一个对象还是多个对象,析构失败。
我们申请的时候申请一组对象,但是调用析构函数析构,却析构的第一个对象,程序就会崩溃,_ptr无法分辨,传入的是一组对象还是一个对象。
不知道使用delete还是delete[]。
int main(void)
{
my_auto_ptr<Object> obja(new Object(10)); // 正确
my_auto_ptr<Object> objb(new Object[10]); // 错误
return 0;
}
总结:
int main()
{
my_auto_ptr<Object> obj(new Object(10));
my_auto_ptr<Object>obja(obj);
使用release可以把obj的资源给obja。
但是执行下面的程序
fun(obj);
int a = obj->value(); //会失去对obj的拥有权。这个语句又无法执行。
}
my_auto_ptr(const _Ty &p):_Owns(p._Owns),_Ptr(p,release())
拷贝构造函数,怎么写都不对,意义不明确,因此就将auto_ptr 弃用了。
由于在c11之前没有移动构造的概念,所以这些问题一直也就解决不了。
因此,由于这一系类问题:对auto_ptr弃用,从从c17中移除。