【C++】RAll,裸指针,弃用auto_ptr原因

什么是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完成返回的是一个对象的地址,
请添加图片描述

  1. 调用my_auto_ptr类的构造函数创建obj对象,
  2. p指向Object不具名对象的地址。
  3. 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中移除。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值