0xzhang的博客

自己实现C++的智能指针

· 0xzhang

作用域§

释放超出作用域的对象。

C++中,RAII依托栈和析构函数,管理内存资源。

因此,在类的析构函数中释放申请的资源即可。离开作用域时,即使在作用域中出现异常,编译器也会自动调用析构函数。

模板化§

template<typename T>
class smart_ptr {
public:
  explicit smart_ptr(T* ptr = nullptr)
          : ptr_(ptr) {}

  ~smart_ptr() {
    delete ptr_;
  }

  T* get() const { return ptr_; }

private:
  T* ptr_;
};

运算符§

*运算符,解引用。

->运算符,指向对象成员。

在布尔表达式中使用。

  T& operator*() const { return *ptr_; }
  T* operator->() const { return ptr_; }
  operator bool() const { return ptr_; }

拷贝构造和赋值§

禁止拷贝行为。

避免指针传递到其它对象中,将导致重复释放的错误。

作为智能指针,自然要尽可能复用一个指针,并且最终能够安全释放。

  smart_ptr(const smart_ptr&) = delete;
  smart_ptr& operator=(const smart_ptr&) = delete;

在拷贝时转移指针。

copy-and-swap idiom

  ...
  smart_ptr(const other&) {
    ptr_ = other.release();
  }
  smart_ptr& operator=(const smart_ptr& rhs) {
    smart_ptr(rhs).swap(*this);
    return *this;
  }
  ...
  T* release() {
    T* ptr = ptr_;
    ptr_ = nullptr;
    return ptr;
  }
  void swap(smart_ptr& rhs) {
    using std::swap;
    swap(ptr_, rhs.ptr_);
  }
  ...

移动语义§

提供移动构造函数后未手动提供拷贝构造函数,后者将自动被删除。

  smart_ptr(smart_ptr&& other) {
  	ptr_ = other.release();
  }
  smart_ptr& operator=(smart_ptr rhs) {
    rhs.swap(*this);
    return *this;
  }

赋值函数为拷贝还是移动,依赖于构造参数采用拷贝还是移动。

引用计数§

以上的实现,基本上满足C++11的unique_ptr。下面将其改进为shared_ptr。

为了使得多个智能指针共享一个对象,添加引用计数功能。

class shared_count {
public:
  shared_count() : count_(1) {}
  void add_count() { ++count_; }
  std::size_t reduce_count() { return --count_; }
  std::size_t get_count() const { return count_; }

private:
  std::size_t count_;
};

类型转换§

子类指针转向基类指针§

添加构造函数,借助模板完成转换。

  template<typename U>
  smart_ptr(smart_ptr<U>&& other) {
    ptr_ = other.release();
  }

指针类型转换§

以dynamic_cast为例,实现如下

template<typename T, typename U>
smart_ptr<T> dynamic_pointer_cast(const smart_ptr<U>& other) {
  T* ptr = dynamic_cast<T*>(other.get());
  return smart_ptr<T>(ptr);
}

完整代码§

当引入第二个模板参数类型U时,不同模板类型实例之间并不能互相访问私有成员。需要显式定义为友元。

在现有smart_ptr基础上,使其同时维护一个引用计数对象。代码如下

class shared_count {
public:
  shared_count() noexcept :count_(1) {}
  void add_count() noexcept {
    ++count_;
  }
  ::std::size_t reduce_count() noexcept {
    return --count_;
  }
  ::std::size_t get_count() noexcept {
    return count_;
  }

private:
  ::std::size_t count_;
};

template <typename T>
class smart_ptr {
public:
  template <typename U>
  friend class smart_ptr;

  explicit smart_ptr(T* ptr = nullptr) :ptr_(ptr) {
    if(ptr_) {
      count_ = new shared_count();
    }
  }
  ~smart_ptr() {
    if(ptr_&&!count_->reduce_count()) {
      delete ptr_;
      delete count_;
    }
  }
  smart_ptr(const smart_ptr& other) {
    ptr_ = other.ptr_;
    if(ptr_) {
      other.count_->add_count();
      count_ = other.count_;
    }
  }
  smart_ptr& operator=(smart_ptr rhs) noexcept {
    rhs.swap(*this);
    return *this;
  }

  smart_ptr(smart_ptr&& other) {
    ptr_ = other.ptr_;
    if(ptr_) {
      count_ = other.count_;
      other.ptr_ = nullptr;
    }
  }

  template <typename U>
  smart_ptr(const smart_ptr<U>& other) noexcept {
    ptr_ = other.ptr_;
    if(ptr_) {
      other.count_->add_count();
      count_ = other.count_;
    }
  }
  template <typename U>
  smart_ptr(smart_ptr<U>&& other) noexcept {
    ptr_ = other.ptr_;
    if(ptr_) {
      count_ = other.count_;
      other.ptr_ = nullptr;
    }
  }
  template <typename U>
  smart_ptr(const smart_ptr<U> other, T* ptr) noexcept {
    ptr_ = ptr;
    if(ptr_) {
      other.count_->add_count();
      count_ = other.count_;
    }
  }

  T* get() const noexcept {
    return ptr_;
  }
  ::std::size_t use_count() const noexcept {
    if(ptr_) {
      return count_->get_count();
    }
    return 0;
  }
  void swap(smart_ptr& rhs) noexcept {
    using ::std::swap;
    swap(ptr_, rhs.ptr_);
    swap(count_, rhs.count_);
  }

  T& operator*() const noexcept {
    return *ptr_;
  }
  T* operator->() const noexcept {
    return ptr_;
  }
  operator bool() const noexcept {
    return ptr_;
  }

private:
  T* ptr_;
  shared_count* count_;
};

template <typename T>
void swap(smart_ptr<T>& lhs, smart_ptr<T>& rhs) noexcept {
  lhs.swap(rhs);
}
template <typename T, typename U>
smart_ptr<T> static_pointer_cast(
  const smart_ptr<U>& other) noexcept {
  T* ptr = static_cast<T*>(other.get());
  return smart_ptr<T>(other, ptr);
}
template <typename T, typename U>
smart_ptr<T> dynamic_pointer_cast(
  const smart_ptr<U>& other) noexcept {
  T* ptr = dynamic_cast<T*>(other.get());
  return smart_ptr<T>(other, ptr);
}
template <typename T, typename U>
smart_ptr<T> reinterpret_pointer_cast(
  const smart_ptr<U>& other) noexcept {
  T* ptr = reinterpret_cast<T*>(other.get());
  return smart_ptr<T>(other, ptr);
}
template <typename T, typename U>
smart_ptr<T> const_pointer_cast(
  const smart_ptr<U>& other) noexcept {
  T* ptr = const_cast<T*>(other.get());
  return smart_ptr<T>(other, ptr);
}

测试代码如下

#include <iostream>
using namespace std;

#include "smart_ptr.h"

class shape {
public:
  virtual ~shape() { puts("~shape()"); }
};

class circle : public shape {
public:
  ~circle() { puts("~circle()"); }
};

int main() {
  cerr << "construct smart_ptr<circle> ptr1" << endl;
  smart_ptr<circle> ptr1(new circle());
  cerr << "use count of ptr1: " << ptr1.use_count() << endl;

  cerr << "use ptr1 copy construct smart_ptr<shape> ptr2" << endl;
  smart_ptr<shape> ptr2;
  cerr << "use count of ptr2: " << ptr2.use_count() << endl;
  ptr2 = ptr1;
  cerr << "use count of ptr2: " << ptr2.use_count() << endl;
  cerr << "use count of ptr1: " << ptr1.use_count() << endl;

  cerr << "use ptr1 move construct smart_ptr<shape> ptr3" << endl;
  smart_ptr<shape> ptr3;
  cerr << "use count of ptr3: " << ptr3.use_count() << endl;
  ptr3 = move(ptr1);
  cerr << "use count of ptr3: " << ptr3.use_count() << endl;
  cerr << "use count of ptr2: " << ptr2.use_count() << endl;
  cerr << "use count of ptr1: " << ptr1.use_count() << endl;

  cerr << "use dynamic_cast convert smart_ptr<shape> ptr2 to smart_ptr<circle> ptr1" << endl;
  ptr1 = dynamic_pointer_cast<circle>(ptr2);
  cerr << "use count of ptr2: " << ptr2.use_count() << endl;
  cerr << "use count of ptr1: " << ptr1.use_count() << endl;

  return 0;
}

输出结果如下

construct smart_ptr<circle> ptr1
use count of ptr1: 1
use ptr1 copy construct smart_ptr<shape> ptr2
use count of ptr2: 0
use count of ptr2: 2
use count of ptr1: 2
use ptr1 move construct smart_ptr<shape> ptr3
use count of ptr3: 0
use count of ptr3: 2
use count of ptr2: 2
use count of ptr1: 0
use dynamic_cast convert smart_ptr<shape> ptr2 to smart_ptr<circle> ptr1
use count of ptr2: 3
use count of ptr1: 3
~circle()
~shape()

参考资料§

  1. 02 | 自己动手,实现C++的智能指针 - 现代C++实战30讲 - 吴咏炜
  2. c++ - What is the copy-and-swap idiom? - Stack Overflow
  3. std::shared_ptr - cppreference.com