C++智能指针 unique_ptr、shared_ptr 的实现

shared_ptr

shared_ptr 是通过引用计数的方式实现的,这里结合一个单线程的参考实现进行解释。注意,这种实现并不是线程安全的。STL 的 std::shared_ptr 也不是线程安全的,两个成员变量的修改并不是一次操作完成的,使用时需要加锁。

参考实现

namespace smart_pointer {
template<typename T>
class SharedPtr {
  public:
    explicit SharedPtr (T* ptr = nullptr) : _ptr(ptr), _pcount(new int(1)) {
    }

    SharedPtr (const SharedPtr& s) : _ptr(s._ptr), _pcount(s._pcount) {
        *(this->_pcount)++;
    }

    ~SharedPtr () {
        if (--(*(this->_pcount)) == 0) {
            delete this->_ptr;
            delete this->_pcount;
            this->_ptr = nullptr;
            this->_pcount = nullptr;
        }
    }

    SharedPtr& operator= (const SharedPtr& s){
        if (this != &s) {
            if (--(*(this->_pcount)) == 0) {
                delete this->_ptr;
                delete this->_pcount;
            }
            this->_ptr = s._ptr;
            this->_pcount = s._pcount;
            *(this->_pcount)++;
        }
        return *this;
    }

    T& operator*() {
        return *(this->_ptr);
    }

    T* operator->() {
        return this->_ptr;
    }

    operator bool () {
        return this->_ptr;
    }

    T* get() const {
        return this->_ptr;
    }

    void swap(SharedPtr& s) {
        std::swap(this->_ptr, s._ptr);
        std::swap(this->_pcount, s._pcount);
    }

  private:
    T* _ptr;      // 指向底层对象的指针
    int* _pcount; // 指向引用计数的指针
};

template<typename T>
void swap(SharedPtr<T>& a, SharedPtr<T>& b) {
    a.swap(b);
}
}; // namespace smart_pointer

int main() {
    SharedPtr<int> p1, p2;
    std::swap(p1, p2);
    return 0;
}

代码说明

  1. 拷贝赋值的时候,要考虑当前所持有的对象是否应该被释放,然后再改变所指对象。
  2. 为了提供程序健壮性,保证 std::swap 在智能指针上能正确工作,需要特化智能指针类型。参考 Effictive C++ 第25款,对其进行特化。由于C++只允许对class template进行偏特化,不允许对function template偏特化,我们想把 SharedPtr<T>::swap 全部加入 std 是不可行的。解决办法是利用C++的名称查找规则,在SharedPtr的命名空间中提供类型匹配的swap,使编译器优先选择该函数使用。
  3. 这种实现方案,在每个底层对象之外,额外开辟了一个int的空间用于引用计数,并由多个指针共享访问。相比于 unique_ptr,shared_ptr 使用了两个指针,多一倍的内存开销。相比于单指针的实现方案,好处是在高频的对象访问上,减少了一次间接访问。

单指针实现方案

namespace smart_pointer {
template<typename T>
class SharedPtr {
  public:
    explicit SharedPtr (T* ptr = nullptr) : _pholder(new ObjectHolder(ptr)) {
    }

    SharedPtr (const SharedPtr& s) : _pholder(s._pholder) {
        this->_pholder->_pcount++;
    }

    ~SharedPtr () {
        if (--(this->_pholder->_pcount) == 0) {
            delete this->_pholder;
            this->_pholder = nullptr;
        }
    }

    SharedPtr& operator= (const SharedPtr& s){
        if (this != &s) {
            if (--(this->_pholder->_pcount) == 0) {
                delete this->_pholder;
            }
            this->_pholder = s._pholder;
            this->_pholder->_pcount++;
        }
        return *this;
    }

    T& operator*() {
        return *(this->_pholder->_ptr);
    }

    T* operator->() {
        return this->_pholder->_ptr;
    }

    operator bool () {
        return this->_pholder->_ptr;
    }

    T* get() const {
        return this->_pholder->_ptr;
    }

    void swap(SharedPtr& s) {
        std::swap(this->_pholder, s._pholder);
    }

  private:
    class ObjectHolder {
        friend class SharedPtr; // 内部类可以任意访问外部类的成员,外部类只有作为友元才能访问内部类私有成员
      public:
        explicit ObjectHolder(T* ptr = nullptr) : _ptr(ptr), _pcount(1) {
        }
        ~ObjectHolder() {
            delete _ptr;
        }
      private:
        T* _ptr;      // 指向底层对象的指针
        int _pcount;  // 引用计数,注意不是指针了
    }; // 每个对象由一个ObjectHolder持有
    ObjectHolder* _pholder; // 指向计数对象的指针
};

template<typename T>
void swap(SharedPtr<T>& a, SharedPtr<T>& b) {
    a.swap(b);
}
}; // namespace smart_pointer

unique_ptr

unique_ptr 使用了 RAII 管理资源,保证了出现异常的情况下,资源也能正确释放。

参考实现

namespace smart_pointer {

template <typename T> 
class PointerDeleter{
  public:
    void operator() (const T *_ptr) {
        if(_ptr) {
            delete _ptr;
            _ptr = nullptr;
        }
    }
};

template <typename T, typename Deleter = PointerDeleter<T> >
class UniquePtr {
  public:
    explicit UniquePtr (T *ptr = nullptr) : _ptr(ptr) {
    }

    ~UniquePtr () {
        Deleter()(this->_ptr);
    }

    // non-copyable
    UniquePtr (const UniquePtr &p) = delete;
    UniquePtr& operator= (const UniquePtr &p) = delete;

    // move constructor
    UniquePtr (UniquePtr &&p) : _ptr(p._ptr) {
        p._ptr = nullptr;
    }

    // move assignment
    UniquePtr& operator= (UniquePtr &&p) {
        std::swap(this->_ptr, p._ptr);
        return *this;
    }

    T& operator*() {
        return *(this->_ptr);
    }

    T* operator->() {
        return this->_ptr;
    }

    operator bool () {
        return this->_ptr;
    }

    T* get() const {
        return this->_ptr;
    }

    T* release() {
        T *pointer = this->_ptr;
        this->_ptr = nullptr;
        return pointer; 
    }

    void reset (T *ptr) {
        UniquePtr<T, Deleter>().swap(*this);
        this->_ptr = ptr;
    }

    void swap(UniquePtr &p) {
        std::swap(this->_ptr, p._ptr);
    }

  private:
    T *_ptr;
};

template<typename T>
void swap(UniquePtr<T>& a, UniquePtr<T>& b) {
    a.swap(b);
}
}; // namespace smart_pointer

代码说明

UniquePtr 需要注意的是保证不会被复制,可以采用的办法有两种。以前的解决办法是把拷贝构造函数和拷贝赋值操作符设为private禁止访问,新的解决办法是将两个函数标记为 delete。我们这里采用的是新标准。

参考

  1. 拓跋阿秀 | 《逆袭进大厂》第二弹之C++进阶篇59问59答(超硬核干货)
  2. lizhentao0707 | 智能指针的原理及实现
  3. Simon | 手写shared_ptr, weak_ptr, unique_ptr aka. github.com/xiaoguangcong
posted @ 2021-02-25 17:34  与MPI做斗争  阅读(445)  评论(0编辑  收藏  举报