2、手写智能指针

要求: 实现一个c++11中shared_ptr的类

在手写一个比较完整的类之前,有一些前置知识需要了解,创建一个空类的时候,编译器会自动生成哪些函数了?

1、默认构造函数;

2、默认拷贝构造函数;

3、默认移动构造函数;

4、默认析构函数;

5、默认赋值运算符函数;

6、默认的移动赋值运算符函数;

我们在编写shared_ptr的时候,关注这6个函数外加上一个有参构造即可

共享指针的由两个指针成员组成,一个是指向对象数据的指针 ,另一个是指向控制卡的指针 。其中控制块中包含引用计数,weak_ptr计数等,这里第一版只考虑引用计数,也就是说我们类的私有成员应该含有T* _ptrsize_t* _count,但这样还不够,当多个线程同时访问同一个线程计数的时候,可能会出现线程安全问题 ,我们可以将引用计数设计成原子变量,即atomic<size_t>* _count

这里有个问题,为什么引用计数要使用指针的形式了?

这里我们的指针是指向引用计数的内存,如果直接定义T _count,那么多个shared_ptr对象共享的不是同一个引用计数变量。

原shared_ptr结构可见下图:

接下来是我们共享指针应该提供几种常见方法,get()(返回裸指针),use_count(返回引用计数)。

这两个函数的逻辑很简单:

复制代码
T* get() {
  return _ptr;
}

size_t use_count() const {// 注意这里是常量成员函数
  return *_count // 访问指针_count指向的引用计数
}

接下来,就是我们文章开头所说的,构建一个空类所涉及到的自动创建的成员函数了
1、默认无参构造函数

复制代码
SharedPtr () : _count(new size_t(1)), _ptr(nullptr) {}

2、有参构造函数

复制代码
SharedPtr (T* ptr) : _count(new size_t(1)), _ptr(ptr) {}

3、拷贝构造函数

复制代码
SharedPtr(const SharedPtr& ptr) 
  : _count(ptr._count), _ptr(Ptr._ptr) {
  ++(*_count); // 自加与自减都前置
}

4、移动构造函数

这里要注意的是,与move语义区分,move语义的本质是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬移或者拷贝。这里的移动构造函数思路是,拷贝一份,然后将原对象清除。

复制代码
// 移动构造不需要增加引用计数
SharedPtr(const SharedPtr&& ptr) 
  : _count(ptr._count), _ptr(ptr._ptr) {
  ptr._count = nullptr;
  ptr._ptr = nullptr;
}

5、拷贝赋值函数

比较难写的就是拷贝赋值函数与移动赋值函数了,这里有一个经典问题:
当有两个shared_ptr指针为p1与p2,分别指向两块不同的内存,令p1=p2,问p1与p2的引用计数分别是多少?赋值的过程中发生了什么?

答:p1与p2的引用计数都为2,p1所指向的内存被释放。

赋值的过程中发生了什么了?

1)p1的引用计数减1;

2)判定该指向内存的引用计数是否为0,为0则释放内存,置空指针(可不必置空,后续会为其赋值);

3)将p2的值赋值给p1;

4)这时p1与p2将指向同一块内存,引用计数自增1;

当然我们这里应该还要考虑两点:

1)为提高效率,避免过多的副本产生,尤其子啊链式赋值的情况下,这里返回值应该为SharedPtr&

2)防止自赋值,应做this == &ptr的判断,若为自赋值,则返回*this

复制代码
SharedPtr& operator=(const SharedPtr& ptr) {
  if (this == &ptr)
    return *this;
  if (_ptr && _count) {// 如果指针与引用计数指针不为空
    if (--(*count) == 0) {
      delete _count;
      delete _ptr;
    }
  }
  // 开始做赋值操作
  _count = ptr._count;
  _ptr = ptr._ptr;
  ++(*_count);
  return *this;
}

6、移动赋值函数

思路与拷贝赋值函数一致。

复制代码
SharedPtr&& operator=(const SharedPtr& ptr) {
  if (this == &ptr)
    return *this;
  if (_ptr && _count) {
    if (--(*_count) == 0) {
      delete _count;
      delete _ptr;
    }
  }
  _ptr = ptr._ptr;
  _count = ptr._count;
  ptr._ptr = nullptr;
  ptr._count = nullptr;
  return *this;
} 

7、析构函数

复制代码
~SharedPtr() {
  if (_count && --(*_count) == 0) {
    delete _count;
    delete _ptr;
  }
}

下面是完整的类
点击查看代码

复制代码
template<class T>
class SharedPtr {
public:
  T* get() {
    return _ptr;
  }

  size_t use_count() const {// 注意这里是常量成员函数
    return *_count // 访问指针_count指向的引用计数
  }

  SharedPtr () : _count(new size_t(1)), _ptr(nullptr) {}

  SharedPtr (T* ptr) : _count(new size_t(1)), _ptr(ptr) {}

  SharedPtr(const SharedPtr& ptr) 
    : _count(ptr._count), _ptr(Ptr._ptr) {
    ++(*_count); // 自加与自减都前置
  }

  // 移动构造不需要增加引用计数
  SharedPtr(const SharedPtr&& ptr) 
    : _count(ptr._count), _ptr(ptr._ptr) {
    ptr._count = nullptr;
    ptr._ptr = nullptr;
  }

  SharedPtr& operator=(const SharedPtr& ptr) {
    if (this == &ptr)
      return *this;
    if (_ptr && _count) {// 如果指针与引用计数指针不为空
      if (--(*count) == 0) {
        delete _count;
        delete _ptr;
      }
    }
    // 开始做赋值操作
    _count = ptr._count;
    _ptr = ptr._ptr;
    ++(*_count);
    return *this;
  }

  SharedPtr&& operator=(const SharedPtr& ptr) {
    if (this == &ptr)
      return *this;
    if (_ptr && _count) {
      if (--(*_count) == 0) {
        delete _count;
        delete _ptr;
      }
    }
    _ptr = ptr._ptr;
    _count = ptr._count;
    ptr._ptr = nullptr;
    ptr._count = nullptr;
    return *this;
  }
  
  ~SharedPtr() {
    if (_count && --(*_count) == 0) {
      delete _count;
      delete _ptr;
    }
  }  
  
private:
  T* _ptr;
  atomic<size_t*> _count;  
};