shared_ptr实现和线程安全分析

一、shared_ptr使用方法

使用该智能指针(或者其他两种)需要导入头文件#include <memory>

  • 创建空指针
std::shared_ptr<int> p1;             //不传入任何实参
std::shared_ptr<int> p2(nullptr);    //传入空指针 nullptr
  • 明确指向
std::shared_ptr<int> p3(new int(10));
std::shared_ptr<int> p4 = make_shared<int>(10);  //推荐使用make_shared<T>构造
  • 拷贝构造
std::shared_ptr<int> p3(new int(10));
std::shared_ptr<int> p5(p3);

除此之外还可以对shared_ptr赋值,通过重写operator=实现。需要注意,对于p1=p2(均为智能指针)这种,p2所指对象由于被p1指向,所以该引用计数会加一,p1原本指向的资源的引用计数会减一。这也会引出下面关于shared_ptr指针的线程安全问题。


二、实现

//存在问题:引用计数不增加

//已解决:重载()函数换成拷贝构造函数

#include <bits/stdc++.h>
using namespace std;

template <class T>
class mShared_ptr {
private:
    T* _ptr;
    int* _refcount;
public:
    mShared_ptr() : _ptr(new T()), _refcount(new int(0)) {}

    mShared_ptr(T* obj) : _ptr(obj), _refcount(new int(1)) {}

    ~mShared_ptr() {
        --(*_refcount);
        if(_ptr && (*_refcount) == 0) {
            delete _ptr;
            delete _refcount;
        }
    }

    mShared_ptr (const mShared_ptr& _other) {
        ++*_other._refcount;
        cout << "_ref : " << *_other._refcount << endl;
        this->_ptr = _other._ptr;
        this->_refcount = _other._refcount;
    }

    mShared_ptr& operator= (const mShared_ptr& _other) {
        if(_other == *this)
            return *this;
        --*(this->_refcount);
        if(this->_ptr && *(this->_refcount) == 0) {
            delete this->_ptr;
            delete this->_refcount;
        }
        ++*_other._refcount;
        cout << "_ref : " << *_other._refcount << endl;
        this->_ptr = _other._ptr;
        this->_refcount = _other._refcount;
        return *this;
    }

    T& operator* () {
        return *_ptr;
    }

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

};

int main(int argc, char const *argv[])
{
    mShared_ptr<int> obj(new int(10));
    mShared_ptr<int> obj2 = obj;
    mShared_ptr<int> obj3(obj);

    cout << *obj << endl;
    cout << *obj2 << endl;
    cout << *obj3 << endl;
    cout << obj.get_refcount() << endl;
    cout << obj2.get_refcount() << endl;
    cout << obj3.get_refcount() << endl;
    return 0;
}

运行结果如下:

root@iZuf6ccxsb4mprtz1c1mx2Z:~/Linux/Test/DataStructure# ./mShared_ptr 
_ref : 2
_ref : 3
10
10
10
3
3
3

这里实现的还有一些问题,因为shared_ptr源码中关于引用计数是原子操作,不需要考虑资源使用冲突的问题,可以在自己实现的时候加锁。


三、关于shared_ptr的线程安全问题

首先什么是线程安全?
简单来说就是多个线程操作一个共享数据,都能按照预期的行为进行,无论多个线程的运行次序如何交织。

对于shared_ptr,其内部有两个变量,引用计数和真正的对象类型指针。其中引用计数是原子操作,所以对于shared_ptr的读操作是线程安全的。

但是对于shared_ptr中赋值如ptr1 = ptr2,需要两个步骤,1、ptr1的内部对象指针Obj1替换成ptr2内部对象Obj2指针;2、ptr1的对于Obj1的引用计数缓存Obj2的引用计数。

这两步并不是原子的,如果一个线程需要对shared_ptr进行赋值操作ptr1 = ptr2,刚完成第一步,就切换到其他线程又对ptr2进行操作,如ptr2 = ptr3,就有可能造成析构了引用计数。而继续之前线程的第二步,就会出错。

总之:对于shared_ptr的读操作是线程安全的。
对于shared_ptr读写操作不是线程安全的,需要加锁。


tips:为什么shared_ptr的引用计数能够同步到不同的指针中?

有人回答可能使用的是static变量,这是不可能的,因为一个类中只有一个静态变量,只能记录对于一个对象的引用次数,这在包含两个shared_ptr以上的程序中是不可行的。
个人认为是引用计数是用指针实现的,指向一个记录引用次数的对象。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容