C++中的智能指针(未完成/修改中)

1.RAlI与智能指针

在了解智能指针的使用之前,让我们先明了为何我们需要使用智能指针技术

1.1 RALL

RAll (Resource Acquisition ls lnitialization) 是由c++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化-即使用局部对象来管理资源的技术;这里的资源主要是指操作系统中有限的东西,如内存(heap)、网络套接字,互斥量,文件句柄等,局部对象是指存储在的对象,它的生命周期是由操作系统来管理的,无需人工介入。

资源的使用经历三个步骤

  • 获取资源(创建资源)
  • 使用资源
  • 销毁资源(析构对象)

RAII方案利用局部对象自动销毁的特点来控制资源的生命周期,基于这样的技术实现的智能指针,能通过自动销毁的特性,避免cpp程序员忘记主动销毁资源而引发不必要的问题。

用大白话讲,智能指针会自动销毁,这样我们就不用担心忘记使用delete或重复调用delete所引发的内存管理问题。

智能指针的出现解决了许多传统指针具有的问题,见下文

1.2 使用传统指针所带来的问题

直接使用传统的指针管理内存会存在以下问题
在这里插入图片描述
总而言之,使用传统的指针在cpp中存在很大的内存管理风险

可以说,相比于传统指针,智能指针:

  • 1.解决了空指针与野指针的问题
    智能指针在cpp中是一个对象,其拥有构造函数,通常情况下,在构造智能指针时需要传入要指向的已存在的对象实体,也因此智能指针不存在空指针(*p = NULL)以及野指针(单创建指针而未指明指向的对象,如 int* p;p为一个野指针)的情况。
  • 2.解决对象重复释放的问题
    智能指针在其生命周期结束时,调用析构函数来实现对指向资源的释放,避免对象不必要的多次释放。
  • 3.解决了内存泄漏问题
    同上,使用智能指针可避免忘记释放内存导致的内存泄漏问题。

2. C++中的智能指针

C++中拥有如下三种智能指针(auto_ptr已经弃用)
在这里插入图片描述
我们从auto_pt被弃用的原因入手,从这个“最基础”的智能指针开始,我们可以了解到为何其功能会不全面,从而帮助我们理解为何需要有其他三种智能指针,以及它们各自所拥有的“新”特性(相对auto_ptr而言)。

1.2auto_ptr及其弃用的原因

下面的代码实例实现了一个auto_ptr

template<class _Ty>
class my_auto_ptr
{
private:
	bool _Owns;	//
	_Ty* _Ptr;	//
publicmy_auto_ptr(_Ty* p = NULL):_Owns(p != NULL)_Ptr(p){}
  
  ~my_auto_ptr()
  {
     if(_Owns){ delete _Ptr;}
     _Owns = false;
     _PTr = NULL;
  }
 
   _Ty* get() const 
   { return _Ptr; }

//以&返回是为得到_Ptr所指向的实体
   _Ty& operator* () const
    { return*(get(); }
  
   _Ty* operator->() const
    { return get(); }

  my_auto_ptr& operalor=(const my_auto_ptr& _Y)
  {
    if (this == &_Y) return *this;
    if (_Owns)
    {  delete _Ptr; }
    _Owns = _Y._Owns;
    _Ptr = _Y.release() ;
    return 0;
  }

};
void fun {
my_auto_ptr<Object>obj(new Object (10));

cout <<obj->Value() << endl ;
cout <<(*obj).Value()<< endl;
 
}

弃用的原因

  • 1.拷贝构造函数与赋值语句的意义不明确
    在这里插入图片描述
    如上例中使用浅拷贝(共享拥有权Owns)会致使同一空间被释放两次,引发程序崩溃;

如果不共享拥有权,改为使用release()函数来实现拥有权的转移.由此在执行赋值或拷贝构造后,原指针的_Owns被设为false,无法再通过原指针访问其对应资源,这有时并不是我们想要的结果.

  • 2.仍未解决传统数组中,对单个对象delete和对数组指针delete[]的冲突问题
int main()
{
 my_auto_ptr<int> pobja (new int[10]);//T
 my_auto_ptr<Object> pobjb(new Object[10]);//F,显然my_auto_ptr中的析构函数无法应对这种情况
 return 0;
}

auto_ptr因为无法解决上述问题,使得auto_ptr无法与STL库中的容器关联

auto_ptr最终于C++11标准中被弃用.于C++17标准中被移除

1.3unique_ptr(唯一型智能指针) 的使用

1.3.1对使用权问题的解决

为解决auto_ptr中使用权Owns的不明确问题,因而引入unique_ptr(唯一型智能指针),该指针相对于auto_ptr不具有bool _Owns,其对资源的使用权具有唯一性,unique_ptr唯一拥有其指向的资源空间(因此不需添加bool _Owns对其标致)

#include<memory> //智能指针存于此库中


template<class _Ty> 
class my_unique_ptr
{
private:
  _Ty*_Ptr;
 my_unique_ptr(const my_unique_ptr&) = delete; //禁用了拷贝构造函数

  my_unique_ptr& operator=(const my_unique_ptr&) = delete;//禁用了赋值语句重载

public:
  typedef _Ty*pointer;
  typedef _Ty element_type;
  
  my_unique_ptr(_Ty* p = NULL):_Ptr(p){}
  ~my_unique_ptr(){ delete _Ptr;}

  //移动构造
  my_unique_ptr(my_unique_ptr&&_Y)
  {
   Ptr = _Y._Ptr;
   _Y._Ptr = nullptr;
  }
   //移动赋值
 my_unique_ptr& operator=(my_unique_ptr&&_Y)
  {
   if(this != &_Y)
   {
    delete _ptr;
    Ptr = _Y._Ptr;
    Y.Ptr = NULL;
   }
   return *this;
  }

  
};

//F,unique_ptr禁用了拷贝构造函数
void fun(std::unique_ptr<Object> test)
{···}


int main()
{
 std::unique_ptr<int> pobja (new int[10]);//T
 
  std::unique_ptr<Object> pobjb(pobja);//F,unique_ptr禁用了拷贝构造函数

  std::unique_ptr<Object> pobjc;
  pobjc = pobja;//F,unique_ptr禁用了=运算符重载函数
  
  std::unique_ptr<Object> pt(std::move(pobja))//使用拷贝移动构造实现构造-拷贝移动构造,实现资源的转移
  
  std::unique_ptr<Object> pt2;
  pt2 = std::move(pobja);//T,底层上将pobja强转为std::unique_ptr<Object>&& 类型

  
 return 0;
}

1.3.2对单个对象与数组指针delete冲突问题的解决

unique_ptr通过辅助类型Deletor的帮助达到对该问题的解决,见下例

//自建辅助类型Deletor对普通类型的接受方案
template<class _Ty>
class MyDeletor
{
public:
  myDeletor(){};
  void operator()(_Ty* ptr) const
  {
    if (ptr != nullptr)
    {
     delete ptr;
    }
  }
};

//对Deletor部分特化,使其能够接收数组类型
template<class _Ty>
class MyDeletor<_Ty[]>
{
public:
  myDeletor() = default ; //c11标准。意为添加一个默认函数,等效于 myDeletor(){};
  void operator()(_Ty* ptr) const
  {
    if (ptr != nullptr)
    {
     delete[]ptr;
    }
  }
};


int main()
{
  MyDeletor<Object> op;
  MyDeletor<Object[]> arop;
//通过自建的辅助类型Deletor达到对不同数据类型的delete
  Object *p = new 0bject(10); 
  op(p);

  p = new Object[10];
  arop(p) ;
}

了解了Deletor的用法,再来重新编写my_unique_ptr

//额外传入 _Dx来接收MyDeletor,使得my_unique_ptr能通过MyDeletor来delete不同数据类型
//下面的函数模板用来处理常规数据类型
template<class _Tyclass _Dx = MyDeletor<_Ty>> 
class my_unique_ptr
{
public:
//using是c11的新用法,等价于typedef
//如using pointer = _Ty*等价于typedef _Ty* pointer
using pointer = _Ty*;
using element_type = _Ty;
using delete_type = _Dx;
private:
  _Ty* _Ptr;
  _Dx  _mydeletor;//创建_Dx类型的实体_mydeletor


public:
  my_unique_ptr(pointer * p = NULL):_Ptr(p){}
   ~my_unique_ptr()
   { 
     if( _Ptr !=NULL)
    {
       _mydeletor(_Ptr);
       _Ptr = NULL;
     }
   }
 
 my_unique_ptr(const my_unique_ptr&) = delete; //禁用拷贝构造
 my_unique_ptr& operator=(const my_unique_ptr&) = delete;//禁用了赋值语句重载

//获取deletor
  _Dx& get_deletor() const
  { return _mydeletor;}
  const  _Dx& get_deletor() const
  { return _mydeletor;}
   _Ty* get()const
   { return _Ptr;}
   
   
   _Ty& operator* () const
    { return*_Ptr; }
  
   _Ty* operator->() const
    { return _Ptr; }

    operator bool()
    { return _Ptr != nullptr } 
     
     //release函数,使指针不再指向原空间,返回原空间地址
    _Ty* release()
    {
      _Ty* old = _Ptr;
      _Ptr = nullptr;
      rerturn old;
    }
    //重置函数,重新定向_Ptr的指向,并将其旧的所拥有的资源释放
   void reset( _Ty* _P = nullptr)
   {
      pointer old = _Ptr;
      _Ptr = _P; 
      if(old != nullptr)
      _mydeletor(old);
   }
   void swap(my_unique_ptr _Y)
   {
     std::swap(_Ptr,_y._Ptr);
     std::swap(_mydeletor._Y._mydeletor);
   }

  my_auto_ptr& operalor=(const my_auto_ptr& _Y)
  {
    if (this == &_Y) return *this;
    if (_Owns)
    {  delete _Ptr; }
    _Owns = _Y._Owns;
    _Ptr = _Y.release() ;
    return 0;
  }

 
 
  //移动构造
  my_unique_ptr(my_unique_ptr&&_Y)
  {
   Ptr = _Y._Ptr;
   _Y._Ptr = nullptr;
  }
   //移动赋值
 my_unique_ptr& operator=(my_unique_ptr&&_Y)
  {
   if(this != &_Y)
   reset(_Y.releas());
   
   return *this;
  }

  
};
//下面的函数模板用来处理数组数据类型
template<class _Tyclass _Dx> 
class my_unique_ptr<_Ty[]._Dx>
{
···;//···表示其余各部分与上例相同

 //额外增加一个对数组下标访问的重载
  _Ty& operator[](size_t Idx) const
  {
     return _Ptr[_Idx];
  }
};
int main()
{
 std::unique_ptr<Objcect> pobja (new Object(10));
 
  std::unique_ptr<Object[]> pobjb(new Object[10]);

make_unique

int main {

std::unique_ptr<Object> op(new Object(10));
std::unique_ptr<Object> op2 = std::make_unique<Object>(100);//两种构建方式等价
;
return 0;
}

1.4shared_ptr(共享型智能指针)的使用&线程安全

在这里插入图片描述

use_count返回引用计数

int main ()
{
std::shared_ptr<Object> sp1(new Object(10)) ;
std::shared_ptr<Object> sp2 = std::make_shared<Object>(20);

std::shared_ptr<Object> sp3;

cout<<sp1.use_count()<<endl; //1
cout<<sp2.use_count()<<endl; //1
cout<<sp3.use_count()<<endl; //0

}

int main ()
{
std::shared_ptr<Object> sp1(new Object(10)) ;
std::shared_ptr<Object> sp2 = std::make_shared<Object>(20);
std::shared_ptr<Object> sp3;

sp3 = sp1;//如果sp3以sp1为参数进行拷贝构造初始化其计数变化与下例相同

cout<<sp1.use_count()<<endl; //2
cout<<sp2.use_count()<<endl; //1
cout<<sp3.use_count()<<endl; //2

}

operator bool 判断指针是否有指向对象

实现如下operatorbool () const { return ptr != nullptr; }


int main ()
{
std::shared_ptr<Object> sp1(new Object(10)) ;

std::shared_ptr<Object> sp2;

if(sp1); //1
if(sp2); //0
return 0;

}

reset 替换指向对象

void reset( _Ty* p = nullptr)
{
if (this->ptr != nullptr & --this->ptr->ref == 0)
  mDclctor(ptr);
  ptr = new RefCnt<_ Ty>(p);
}

线程安全

在多线程中,使用atomic 原子操作来防止+,-等非原子操作所引发的异常
include<atomic>是包含原子操作的库

weak_ptr的引入
在这里插入图片描述
如图,在上例中,因实例之间的循环套用,导致其生命周期结束时,计数器仍未减至0

1.5weak_ptr

在这里插入图片描述

weak_ptr 作为弱引用指针不具备对对象的引用(该功能由强引用指针实现),其仅指向引用计数器 RefCnt,并且以此来解决环形引用的问题
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
对循环问题的解决
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值