智能指针的使用

独占型智能指针

auto_ptr由于在拷贝和赋值时会转移指针权限,无法在STL容器中使用,在c++17中已经删除

unique_ptr为独占式智能指针,无法进行拷贝和赋值,这里我们利用RAII机制来实现一个简单的unique_ptr

这里使用泛型编程,unique_ptr内部保存T指针即可,在构造函数保存指针,析构函数释放内存

template<typename T>
class unique_ptr
{ 
public:
      unique_ptr(T *ptr = nullptr) : ptr_(ptr)
	  {
	  }
      ~unique_ptr()
	  {
		  Release();
	  }
private:
      void Release()
      {
          if(ptr_)
            delete ptr_;
      }
private:
     T          *ptr_;
};

独占式智能指针,所以我们将拷贝构造函数和赋值运算符禁用,并且加入移动构造函数和移动赋值运算符

template<typename T>
class unique_ptr
{
pubilc:
    //移动构造,不要忘了noexcept 
	unique_ptr(unique_ptr<T> &&rhs) noexcept : ptr_(rhs.ptr_)
	{
         rhs.ptr_ = nullptr;
	}

    unique_ptr<T>& operator=(unique_ptr<T> &&rhs)
	{
	    //先释放自己管理的指针
		Release();
		//保存rhs的指针
        ptr_ = rhs.ptr_;
		//将rhs的指针设为空
		rhs.ptr_ = nullptr;
	}
	//禁用拷贝构造
	unique_ptr(const unique_ptr<T> &) = delete;
	void operator=(const unique_ptr<T>&) = delete;
};

再加入一些指针的运算符重载,让使用者能够访问指针

template<typename T>
class unique_ptr{
public:
      void reset(T *ptr = nullptr)
	  {
	      //释放自身管理的指针
		  Release();
		  //
		  ptr_ = ptr;
	  }
      void swap(unique_ptr<T> &rhs)
	  {
	      //交换两个unique_ptr内部指针即可
          std::swap(ptr_,rhs.ptr_);
	  }
	  
	  void get() const{ return ptr_;  }
      T* operator->() { return ptr_;   }
	  T& operator*()  { return *ptr_;  }
	  operator bool() { return !!ptr_; }
};
          

最后我们使用可变参数模板和完美转发实现make_unique,这样就完成了一个基本的unique_ptr了

/**
* @description:  创建一个unique_ptr
* @param {T}     对象类型
* @param {Args...} 构造对象的参数
* @return {*}
*/	
template<typename T,typename ...Args>
unique_ptr<T> make_unique(Args&&... args)
{
	return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

完整代码如下:

    template<typename T>
	class unique_ptr
	{
    public:
           unique_ptr(T *ptr = nullptr) : ptr_(ptr)
		   {
		   }
		   ~unique_ptr()
		   {
			   Release();
		   }

		   unique_ptr(unique_ptr<T> &&rhs) noexcept : ptr_(rhs.ptr_)
		   {
                 rhs.ptr_ = nullptr;
		   }

           unique_ptr<T>& operator=(unique_ptr<T> &&rhs)
		   {
			     Release();
                 ptr_ = rhs.ptr_;
				 rhs.ptr_ = nullptr;
		   }
		   void reset(T *ptr = nullptr)
		   {
			    Release();
				ptr_ = ptr;
		   }
           void swap(unique_ptr<T> &rhs)
		   {
                std::swap(ptr_,rhs.ptr_);
		   }
		   void get() const{ return ptr_;  }
           T* operator->() { return ptr_;   }
		   T& operator*()  { return *ptr_;  }
		   operator bool() { return !!ptr_; }

		   unique_ptr(const unique_ptr<T> &) = delete;
		   void operator=(const unique_ptr<T>&) = delete;

    private:
	       void Release()
		   {
			   if(ptr_)
			     delete ptr_;
		   }
	private:
	      T   *ptr_;
	};
    
    
    /**
    * @description:  创建一个unique_ptr
    * @param {T}     对象类型
	* @param {Args...} 构造对象的参数
    * @return {*}
    */	
	template<typename T,typename ...Args>
	unique_ptr<T> make_unique(Args&&... args)
	{
		return unique_ptr<T>(new T(std::forward<Args>(args)...));
	}

共享型智能指针

线程安全

先来分析一下标准库shared_ptr的线程安全问题,我们都知道shared_ptr是引用计数型智能指针,能够在多线程下安全释放内存,但这并不代表多个线程访问同一个shared_ptr是线程安全的

我们来看看多线程访问同一个shared_ptr的示例(此代码运行会coredump)

//全局的智能指针
std::shared_ptr<int>  num(new int(10));
void test04()
{
    //创建一个子线程访问智能指针
	std::thread t([]()
	{
	    //死循环读取num的值
		for(;;)
		{
			auto it = num;
		    int  tmp = *num;
		}
	});
	//主线程休眠一秒
    sleep(1);
    //将智能指针reset
	num.reset();
}

  1. 在主线程休眠后,此时子线程正在对shared_ptr进行读操作,主线程对shared_ptr进行写操作(reset),此时的状态如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4uTqb9S1-1639643217240)(https://note.youdao.com/yws/res/10266/WEBRESOURCEc1acd7b34877386670966059f66ad0d4)]

  1. 由于主线程将shared_ptr进行reset,导致shared_ptr将num的内存进行释放,而此时子线程又刚好在访问num,这就访问内存非法coredump了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ps5fJ49p-1639643217242)(https://note.youdao.com/yws/res/10260/WEBRESOURCE179ca3c7d09abf1e42bcd2de0d1d8560)]

我们可以得出以下结论:

  • 多个线程对同一个shared_ptr进行读写是线程不安全的,拷贝,访问等都是读操作,析构,移动,reset都是写操作
  • 多个线程对同一个shared_ptr进行读操作是线程安全的
  • 多个线程对共享同一个引用计数的不同shared_ptr进行读写操作是线程安全的

shared_ptr

网上大部分都是实现一个简单非线程安全引用计数的shared_ptr,这里我们参考标准库实现一个线程安全含有强弱引用计数的shared_ptr,完成shared_ptr和weak_ptr的全部功能,再也不怕面试问智能指针了

引用计数

这里我们使用c++11提供atomic来实现引用计数对象,包含强弱两个引用计数,对外提供增加和减少引用计数的接口即可

class SharedRefCount
{
public:
     SharedRefCount() : WeakRefCountImpl_(1),
                        RefCountImpl_(1)
     {}
     ~SharedRefCount() = default;
     /**
       * @description:  将引用计数加一
       * @param {*}
       * @return {*}
     */	
     void IncrementRef() { ++RefCountImpl_; }
     /**
       * @description:  将引用计数减一,并获取原子操作减一后的值
       * @param {*}
       * @return {long} 引用计数值
     */	
     long DecrementRef() { return --RefCountImpl_; }
      /**
       * @description:  将弱引用计数加一
       * @param {*}
       * @return {*}
      */	
     void IncrementWeak() { ++WeakRefCountImpl_; }
      /**
       * @description:  将弱引用计数减一,并获取原子操作减一后的值
       * @param {*}
       * @return {long} 弱引用计数值
      */	
     long DecrementWeak() { return --WeakRefCountImpl_;}
     
     /**
       * @description:  获取强引用计数值
       * @param {*}
       * @return {long} 强引用计数值
      */
      long use_count() { return RefCountImpl_.load(std::memory_order_acquire)}
      
private:
    std::atomic<long> WeakRefCountImpl_;
    std::atomic<long> RefCountImpl_;
};

我们再提供一个lock接口,在强引用计数不为0的情况下将强引用计数加一,这里需要用到CAS操作

    /**
      * @description: 在强引用计数不为0的情况下进行加一 
      * @param {*}
      * @return {bool} 1 成功 | 0 失败
    */	
    bool lock()
    {
        //获取单个变量,这里我们用relaxed松散型内存序即可
        long num = RefCountImpl_.load(std::memory_order_relaxed);
    
        //CAS操作使用while循环不断获取引用计数的值,如果大于0则继续尝试加一
        do
        {
            if(num == 0)
              return false;
            //CAS失败后,会将原子变量值保存在num中
        }while(!RefCountImpl_compare_exchange_weak(num,num+1,std::memeory_acq_rel,std::memory_order_relaxed));
        
        return true;
    }

内存序

* @param memory_order_relaxed 只保证自身操作为原子性,不会影响其他变量的读写操作顺序
* @param memory_order_consume 用于读操作,对于内存序的影响:当前线程中consume操作之后依赖于该consume操作不能排到该consume操作之前
* @param memory_order_acquire 用于读操作,对于内存序的影响:当前线程中acquire之后的读写操作不能排到acquire之前
*                             其他线程对于该原子变量release操作以及之前的写入都在acquire操作之后可见 
* @param memory_order_release 适用于写操作,对于内存序的影响:该操作之前的读写都不能排到该操作之后
*                             release写入的数据,对于其他线程对该原子变量的acquire操作和release之后的操作可见
* @param memory_order_acq_rel 即是acquire操作也是release操作,read-modify-write操作
* @param memory_order_seq_cst 用于load,release,read-modify-write操作,读操作时为acquire,写操作时为release,读-改-写操作时为acq_rel
*/

Attribute

实现shared_ptr需要三个成员变量

  • 对象指针
  • 引用计数指针
  • 删除器
template<typename T>
class shared_ptr
{
     //std::function使用类型擦除来提供强大的泛型能力,可以保存lambda表达式,函数指针,仿函数
     using Deleter = std::function<void(T*)>;
private:
     //对象指针
     T                  *ptr_;
     //引用计数指针
     SharedRefCount     *RefCount_;
     //自定义删除器
     Deleter             deleter_;
};

Method

shared_ptr是可以进行拷贝和移动的,所以我们要为其提供拷贝构造函数和移动构造函数

template<typename T>
class shared_ptr
{
public:
     //默认构造函数
     shared_ptr(T *ptr = nullptr,const Deleter &deleter = Deleter()) :
                                       ptr_(ptr),
                                       deleter_(deleter)
    {
        if(!_ptr)
           return;
        //对象指针不为空,那么就构造引用计数对象
        RefCount_  = new SharedRefCount();
    }
    //拷贝构造函数
    shared_ptr(const shared_ptr<T> &rhs) : ptr_(rhs.ptr_),
                                           RefCount_(rhs.RefCount_),
                                           deleter_(rhs.deleter_)
    {
        //对象指针不为空,那么就将引用计数加一
        if(ptr_)
          RefCount_->IncrementRef();    
    }
    //移动构造函数
    shared_ptr(shared<T> &&rhs) noexcept : ptr_(rhs.ptr_),
                                           RefCount_(rhs.RefCount_),
                                           deleter_(rhs.deleter_)
    {
        //将移动的对象置空
        rhs.ptr_      = nullptr;
        rhs.RefCount_ = nullptr;
        rhs.deleter_  = Deleter();
    }
};

提供两个构造函数模板,使我们的智能指针也能让父类指针指向子类对象,实现多态!

template<typename T>
class shared_ptr
{
public:
    //拷贝构造函数模板
    template<typename U>
    shared_ptr(const shared_ptr<U> &rhs,T *ptr) : ptr_(ptr_),
                                                  RefCount_(rhs.RefCount_)
    {
        if(ptr_)
          RefCount_->IncrementRef();
    }
    //构造函数模板
    template<typename U>
    shared_ptr(U *ptr = nullptr,const Deleter &deleter = Deleter()) :
                                       ptr_(ptr),
                                       deleter_(deleter)
    {
        if(!_ptr)
           return;
        //对象指针不为空,那么就构造引用计数对象
        RefCount_  = new SharedRefCount();
    }
};

/**
* @description:  将shared_ptr<U> 转换为 shared_ptr<T> 
* @param {T}     目标类型
* @param {U}     源类型
* @return {shared_ptr<T>}    转换后的智能指针
*/    
  template<typename T,typename U>
  shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U> &rhs)
  {
	 //使用dynamic_cast安全转换指针类型
	 T *ptr = dynamic_cast<T*>(rhs.get());
	 
	 return shared_ptr<T>(rhs,ptr);
  }

使用方法如下:

    //A : Base          B : Drivered
    KH::shared_ptr<A> p1(new B());
	KH::shared_ptr<B> p2(new B());
	KH::shared_ptr<A> p3 = KH::dynamic_pointer_cast<A>(p2);

相应的我们应该也提供赋值运算符和移动赋值运算符

   //这里我们使用了一点小技巧,参数不再是引用对象
   shared_ptr<T>& operator=(shared_ptr<T> rhs)
   {
         rhs.swap(*this);
	     return *this;
   }
   shared_ptr<T>& operator=(shared_ptr<T> &&rhs)
   {
      //先释放自身管理的指针
        Release();
      //浅拷贝
	    ptr_         = rhs.ptr_;
	    deleter_     = std::move(rhs.deleter_);
	    RefCount_    = rhs.RefCount_;
      //将rhs设为空
	    rhs.ptr_     = nullptr;
	    RefCount_    = nullptr;
	    rhs.deleter_ = Deleter();

	    return *this;
   }

这里我们重点讲一下赋值运算符的写法,这里我们要考虑到异常安全和自我赋值的情况,我们先看看下面这种异常不安全的写法 :

  1. 判断是否为自我赋值
  2. 释放自身管理的指针
  3. 拷贝对象
shared_ptr<T>& opeartor(const shared_ptr<T> &rhs)
{
    //1. 判断是否为自我赋值
    if(&rhs == this)
        return;
        
    //2. 释放自身管理的指针
    Release();
    //3. 拷贝对象
    ptr_      = rhs.ptr_;
    RefCount_ = rhs.RefCount_;
    deleter_  = rhs.deleter_;
        
    if(ptr_)
       RefCount_->IncrementRef();
}
    

如果在拷贝时出现异常,导致拷贝失败那么这个shared_ptr就处于一种中间状态,因为在拷贝前我们将自身的指针释放了,而上面的写法即使拷贝失败,我们还是处于原来的状态,并有破坏原有结构,我们通过构造一个参数的临时对象来进行拷贝,拷贝完成后再进行swap,这样就能保证异常安全。

这里我们再来看看std::swap的实现,它的实现也是异常安全的

template<typename T>
void swap(T &lhs,T &rhs)
{
    //通过移动对象来避免拷贝
    T tmp = std::move(lhs);
    lhs   = std::move(rhs);
    rhs   = std::move(tmp);
}

同样我们应该提供一个swap函数来交换两个shared_ptr

void swap(shared_ptr<T> &rhs)
{
	std::swap(ptr_,rhs.ptr_);
    std::swap(RefCount_,rhs.RefCount_);
    std::swap(deleter_,rhs.deleter_);
}

我们再来完善shared_ptr的其他接口,实现shared_ptr的全部功能

  long use_count() 
  { 
	  return RefCount_ ? RefCount_->use_count() : 0; 	 
  }

  T* get() const{ return ptr_; }      
  
  void reset(T * ptr = nullptr)
  {
	  Release();
	  ptr_ = ptr;
		  
	  if(ptr_)
		RefCount_ = new SharedRefCount(); 
  }
  bool unique()
  {
      return RefCount_ ? RefCount_->use_count() == 1 : false; 	 
  }
  operator bool()         { return !!ptr_; }
  T* operator->()    const{ return ptr_; }
  T& operator*()     const{ return *ptr_;}
	  
  bool operator==(const shared_ptr<T> &rhs) const { return ptr_ == rhs.ptr_;}
  bool operator!=(const shared_ptr<T> &rhs) const { return ptr_ != rhs.ptr_;}

这里我们通过可变参模板和完美转发来简单实现一个make_shared,标准库的实现更加复杂一些,只进行一次内存分配将引用计数和对象一起构造,减少了内存碎片的产生

template<typename T,typename ...Args>
inline shared_ptr<T> make_shared(Args&& ...args)
{
    return shared_ptr<T>(std::forward(args)...);
}

引用计数对象释放问题

对象的删除策略很简单,只需要在强引用计数为0时释放对象即可,但是不要忘了我们还需要将引用计数对象也进行释放,显然我们不能在强引用计数为0时就释放引用计数对象,因为这时可能还有weak_ptr在监视,如果我们直接将引用计数对象释放了,那么weak_ptr进行访问就会coredump,那我们能否在强弱引用计数都为0的时候释放呢?

我们来分析一下能否在强弱引用计数都为0时释放引用计数对象

  1. 此时有一个shared_ptr和一个监视的weak_ptr在两个不同的线程,强弱引用计数都为1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PFwQyJsb-1639643217242)(https://note.youdao.com/yws/res/10265/WEBRESOURCE067fd0a04d03cb7077b640cb4aa00175)]

  1. 两个线程同时释放shared_ptr和weak_ptr, shared_ptr通过CAS操作将RefCountImpl减1,weak_ptr通过CAS操作将WeakRefCountImpl减一

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g1ai0YIw-1639643217243)(https://note.youdao.com/yws/res/10258/WEBRESOURCE6f0490d52c8b4d6551b9b0027a02cc79)]

  1. 此时强弱引用计数都为0,那么shared_ptr和weak_ptr都会去释放引用计数对象,就会产生double free的异常情况

  2. 我们仔细思考一下,应该在强弱引用计数都为0时释放对象,这说明强弱引用计数两个变量减一为0应该为原子操作,那仅通过单一变量的原子操作是没办法做到的,我们可以使用锁来保护临界区,这确实是一种解决办法,但是标准库却仅使用原子操作巧完成了引用计数对象的释放

解决方案: 在弱引用计数为0时释放引用计数对象

我们回顾一下引用计数对象的实现,在构造函数中我们将RefCountImpl初始化为1,因为我们只会在shared_ptr里构造引用计数对象,所以强引用计数初始为1。这里我们将WeakRefCountImpl也初始化为1,这个1是用来监控是否还有shared_ptr对象存在,只有在最后一个shared_ptr释放时,将强引用计数减为0时,才会将这个1减去。

class SharedRefCount
{
public:
     SharedRefCount() : WeakRefCountImpl_(1),
                        RefCountImpl_(1)
     {}
};

我们再来分析一下这个情况

1.还是一样此时有一个shared_ptr和一个监视的weak_ptr在两个不同的线程,区别是强引用计数为1,弱引用计数为2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-93sWuCFP-1639643217244)(https://note.youdao.com/yws/res/10252/WEBRESOURCEa60ce6ca57c468a253a6160c2f874992)]

2.此时shared_ptr和weak_ptr同时释放,shared_ptr将强引用计数减一,weak_ptr将弱引用计数减一,此时强引用计数为0,弱引用计数为1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dulq2WHH-1639643217244)(https://note.youdao.com/yws/res/10264/WEBRESOURCEb31cce80f8132ba115103400a52a4454)]

3.weak_ptr发现弱引用计数为1则没有去释放对象,而此时shared_ptr发现已经将强引用计数减为0了,则会去减去最后一个监视的弱引用计数,发现弱引用计数为0了,则释放引用计数对象
,这样就完美解决了引用计数对象double free的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NOwIIyex-1639643217245)(https://note.youdao.com/yws/res/10254/WEBRESOURCEee37a34da08bff970e6704a55dc4626a)]

最后我们将释放操作的代码完成,至此就实现了一个完整的shared_ptr了

void destory_obj()
{
    //有自定义删除器
    if(deleter_)
    {
        deleter_(ptr_);
    }else
    {
        //没有删除器则使用delete
        delete ptr_;
    }
}
void Release()
{
   //空指针无需操作
    if(!ptr_)
      return;
      
   //将强引用计数减为0进行释放操作
   if(RefCount_->DecrementRef() == 0)
   {
       destory_obj();
       //将弱引用计数减一,减一后为0则将引用计数对象释放
       if(RefCount_->DecrementWeak() == 0)
         delete RefCount_;
   }
  
   ptr_      = nullptr;
   RefCount_ = nullptr;
}

weak_ptr

weak_ptr的成员变量与shared_ptr一样,但是weak_ptr仅用来监控shared_ptr,并不掌控对象的生命周期

template<typename T>
class weak_ptr
{
public:
       using Deleter = std::function<void(T*)>;
       
private:
     T                 *ptr_;
     SharedRefCount    *RefCount_;
     Deleter           deleter_;
};

weak_ptr的实现与shared_ptr类似,每次拷贝时将弱引用计数加一,析构时将弱引用计数减一即可


template<typename T>
class weak_ptr
{
public:
       weak_ptr() : ptr_(nullptr),
                    RefCount_(nullptr){}
                    
       weak_ptr(const shared_ptr<T> &rhs) : ptr_(rhs.ptr_),
                                            RefCount_(rhs.RefCount),
                                            deleter_(rhs.deleter_)
       {
           //增加弱引用计数
           if(RefCount_)
           {
               RefCount_->IncreamentWeak();
           }
       }
       //对weak_ptr拷贝
        weak_ptr(const weak_ptr<T> &rhs) : ptr_(rhs.ptr_),
	    				                   RefCount_(rhs.RefCount_),
					                       deleter_(rhs.deleter_)
	    {
	        //增加弱引用计数
		    if(RefCount_)
		    {
 			   RefCount_->IncreamentWeak();
		    }
 
	    }
	    //移动构造
	    weak_ptr(weak_ptr<T> &&rhs) noexcept : ptr_(rhs.ptr_),
		    				                   RefCount_(rhs.RefCount_),
						                       deleter_(rhs.deleter_)
	    {
		    rhs.ptr_ 	  = nullptr;
		    rhs.RefCount_ = nullptr;
		    deleter_ 	  = Deleter();
	    }
        //赋值运算符
	    weak_ptr& operator=(weak_ptr<T> rhs)
	    {
		   
		    //临时对象析构时会释放 
		    rhs.swap(*this);
		    return *this;
	    }
       
        weak_ptr& operator=(const shared_ptr<T> &rhs)
		{
			//构造临时对象进行交换,保证异常安全
			weak_ptr(rhs).swap(*this);

			return *this;
		}
		//交换两个weak_ptr
	    void swap(weak_ptr<T> &rhs)
	    {
			std::swap(ptr_,rhs.ptr_);
			std::swap(RefCount_,rhs.RefCount_);
			std::swap(deleter_,rhs.deleter_);
	    }
	    
    
};

我们再来实现expried和lock两个接口,用来判断对象是否过期和构造一个shared_ptr

bool expried()
{
    //空指针
    if(!ptr_)
       return true;
    //强引用计数是为0则expried
    return RefCount_  ? RefCount_->use_count() == 0  : true;
}

shared_ptr<T> lock()
{
    //已经expried,就返回一个空shared_ptr
    if(expried())
      return shared_ptr<T>();
    //使用自身构造一个shared_ptr
    return shared_ptr<T>(*this);
}

释放操作跟shared_ptr类似,每次析构时将弱引用计数减一,弱引用计数为0时则释放引用计数对象

void Release()
{
	if(!ptr_ || !RefCount_)
		  return;
    //弱引用计数减为0则释放引用计数
	if(RefCount_->DecreamentWeak() == 0)
		  delete RefCount_;

	ptr_      = nullptr;
	RefCount_ = nullptr;
	deleter_  = Deleter();  
}

enable_shared_from_this

有时候我们需要在对象内部返回一个shared_ptr,这时我们便需要使用enable_shared_from_this这个小工具了,实现思路也很简单,我们只需要在enable_shared_from_this中保存一个weak_ptr监控外部的shared_ptr即可,在需要时就可以使用这个weak_ptr进行构造了

template<typename T>
class enable_shared_from_this
{
public:
       enable_shared_from_this() = default;
	  ~enable_shared_from_this() = default;
	  enable_shared_from_this(const enable_shared_from_this &rhs) : _M_weak_this(rhs._M_weak_this)
	  {
	  }
      shared_ptr<T> shared_from_this()
	  {
		  return  shared_ptr<T>(_M_weak_this);
	  }
	  weak_ptr<T>  shared_from_this_weak()
	  {
		  return   _M_weak_this;
	  }
private:
     //内部weak_ptr用于监控外部shared_ptr
     weak_ptr<T> _M_weak_this;
};

enable_shared_from_this类的实现非常简单,但是我们面临一个问题,应该在什么时候去初始化这个内部的weak_ptr呢,这里就需要用到c++元编程的知识了,这个weak_ptr是用来监视shared_ptr,所以肯定得在shared_ptr的构造函数中去初始化这个weak_ptr,在编译期判断T是否含有_M_weak_this这个成员变量,如果含有这个成员变量则生成初始化这个变量的代码

由于_M_weak_this为私有成员,所以这里需要再写一个enable_shared_helper友元类,来进行编译期间的判断

//泛化版本类模板,无法推断则继承std::false_type,生成编译期常量value = false
template<typename T,typename = void_t<>>  
struct enable_shared_helper : std::false_type
{};
//类模板偏特化,如果能推断出T::_M_weak_this,则继承std::true_type,会生成一个编译期常量value = true
template<typename T>
struct enable_shared_helper<T,void_t<decltype(T::_M_weak_this)>> : std::true_type
{};

有了enable_shared_helper,我们便可以使用c++17的if constexpr在编译期进行判断并初始化weak_ptr了,我们修改一下shared_ptr的构造函数

shared_ptr(T *ptr = nullptr,const Deleter &deleter = Deleter()) :
		    ptr_(ptr),
		    RefCount_(nullptr),
		    deleter_(deleter)
{
            if(!ptr_)
			  return;

		    RefCount_  = new SharedRefCount();
			//编译期判断T是否含有_M_weak_this
            if constexpr(enable_shared_helper<T>::value)
			{
			    //含有此成员,则进行初始化
                ptr_->_M_weak_this = *this;
			}
			    
}

我们再来测试一下enable_shared_from_this的功能

class A : public KH::enable_shared_from_this<A>
{
public:
	   A(int num) : m_a(num)
	   {
		 TRACE("构造函数调用了");
	   }
       KH::shared_ptr<A>  shared()
	   {
		   return shared_from_this();
	   }     
	   int m_a;
	   ~A()
	   {
		   TRACE("析构函数调用了");
	   }
};
void test05()
{
    //构造A对象的shared_ptr
	KH::shared_ptr<A> p(new A(5));
    //从A对象内部获取一个shared_ptr
	auto it = p->shared();
    //通过此shared_ptr获取成员变量
    TRACE(it->m_a);
}

可以看到enable_shared_from_this的功能实现正常

//输出
//[ /root/Code/http/test/smart_ptr_test.cpp : A : 559 ] : 构造函数调用了
//[ /root/Code/http/test/smart_ptr_test.cpp : test05 : 578 ] : 5
//[ /root/Code/http/test/smart_ptr_test.cpp : ~A : 568 ] : 析构函数调用了
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值