shared_ptr 模拟实现

智能指针原理

智能指针基本上就是利用 RAII 技术实现的。资源取得时机便是初始化时机(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源的技术。在对象构造时获取资源,接着控制对资源的访问,使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源

智能指针的实现要考虑的问题:

  1. 怎么实现 RAII
  2. 如何重载 operator* 和 opertaor->
  3. 怎么复制智能指针对象

前两个问题比较简单,本文就不多做赘述了。下文主要介绍如何正确实现 shared_ptr 的复制函数。

shared_ptr

shared_ptr 这种智能指针访问对象采用共享所有权来管理其生存期。没有哪个特定的 shared_ptr 拥有该对象。取而代之的是,所有指涉到它的 shared_ptr 共同协作,确保在不再需要该对象的时刻将其析构。当最后一个指涉到某对象的 shared_ptr 不再指涉到它时,该 shared_ptr 会析构其指涉到的对象。

因此需要知道有多少个 shared_ptr 指向同一个资源。我们可以用一个引用计数,该计数在构造的时候初始化,复制的时候只需要将该值加 1 即可。经复制得到的对象共享同一个引用计数。

用 static int 实现引用计数可以吗?

既然我们需要多个对象共享同一个引用计数,那使用静态变量可以吗?

这是不行的,使用静态变量是可以做到共享,但静态变量在整个类中只有一个。也就是说,我们定义出的所有对象,共享同一个计数,这明显是不行的。

用 int 实现引用计数可以吗?

很明显不可以。当我们用复制一个 shared_ptr 类型对象时,我们获取到的是 int 类型的值,两个对象没有任何关联。

那用指针实现引用计数可以吗?

这是可以的。

假如我们用 int* 实现计数。在构造函数中,我们为指针 new 一块空间,并初始化为 1,表示有一个 shared_ptr 指向该对象。在复制构造函数中,我们直接复制指针的值,这样两个指针指向的实际是同一块空间,将指向的空间的值加 1 即可。

上面这样在多线程情况下会有问题,因此我们需要用一些方式来保护对临界资源的访问。下面分别用互斥锁和原子变量两种方式实现。

互斥锁

#include 
#include 
using namespace std;

template<class T>
class SharedPtr {
 public:
  SharedPtr(T* ptr) 
      : _data(ptr),
        _cnt(new int(1)),
        _mtx(new mutex) {
    cout << "构造函数" << endl;
  } 

  ~SharedPtr() {
    bool deleteFlag = false;
    _mtx->lock();
    if ((*_cnt)-- == 1) {
      delete _data;
      delete _cnt;
      deleteFlag = true;
    }
    _mtx->unlock();
    if (deleteFlag) {
      cout << "析构函数" << endl;
      delete _mtx;
    }
  }

  SharedPtr(const SharedPtr<T>& sp) 
      : _data(sp._data),
        _cnt(sp._cnt),
        _mtx(sp._mtx) {
    _mtx->lock();
    (*_cnt)++;
    _mtx->unlock();
  }

  SharedPtr<T>& operator=(const SharedPtr<T>& sp) {
    SharedPtr<T> tmp = sp;
    Swap(tmp);
    return *this;
  }

 private:
  void Swap(SharedPtr<T>& other) {
    std::swap(_data, other._data);
    std::swap(_cnt, other._cnt);
    std::swap(_mtx, other._mtx);
  }

 private:
  T* _data;
  int* _cnt;
  mutex* _mtx;
};

int main() {
  SharedPtr<int> sp1(new int(10));
  SharedPtr<int> sp2(sp1);
  SharedPtr<int> sp3(new int(1));
  sp3 = sp2;
  return 0;
}

原子变量

在上面的实现方式中,我们用了互斥锁来保护计数,以实现原子性的加减。我们也可以直接用 C++ 提供的原子类型变量。

#include 
#include 
using namespace std;

class RefCnt {
 public:
  RefCnt()
      : _cnt(1) {}

  int AddRef() {
    return ++_cnt;
  }

  int SubRef() {
    return --_cnt;
  }

 private:
  atomic<int> _cnt;
};

template<class T>
class SharedPtr {
 public:
  SharedPtr(T* ptr) 
      : _data(ptr),
        _refCnt(new RefCnt) {
    cout << "构造函数" << endl;
  }

  ~SharedPtr() {
    if (_refCnt->SubRef() == 0) {
      delete _data;
      delete _refCnt;
      cout << "析构函数" << endl;
    }
  }

  SharedPtr(const SharedPtr<T>& sp) 
      : _data(sp._data),
        _refCnt(sp._refCnt) {
    _refCnt->AddRef();
  }

  SharedPtr<T>& operator=(const SharedPtr<T>& sp) {
    SharedPtr<T> tmp = sp;
    Swap(tmp);
    return *this;
  }

 private:
  void Swap(SharedPtr<T>& other) {
    std::swap(_data, other._data);
    std::swap(_refCnt, other._refCnt);
  }

 private:
  T* _data;
  RefCnt* _refCnt;
};

int main() {
  SharedPtr<int> sp1(new int(10));
  SharedPtr<int> sp2(sp1);
  SharedPtr<int> sp3(new int(1));
  sp3 = sp2;
  return 0;
}

代码比起使用互斥锁的方法简单了不少。

你可能感兴趣的:(C++,c++,数据结构)