自己实现C++智能指针

2,038 阅读3分钟

C++11的智能指针是继可变模板参数,右值引用与移动语义以后又一个非常强大的特性。由于C++是没有GC的语言,程序员需要自己去管理堆上分配的内存。智能指针在我的理解看来实现了部分GC的功能,本质还是使用的引用计数。当RC变到0的时候自动释放相关内存。本质是对一个裸指针进行了封装加上了引用计数罢了。智能指针有四种(其实C++11引入的是三种)unique_ptr shared_ptr weak_ptr和基本被废弃的auto_ptr。下面就针对不同种类的智能指针展开记录。// TODO enable_shared_from_this

unique_ptr

顾名思义对象是unique的,同一时间只有一个智能指针持有对应的对象。因此unique_ptr delete了赋值与拷贝构造,只有移动构造。unique_ptr生命周期结束后就会调用里面裸指针指向的对象的deleter,默认是析构函数,也可以在创建unique_ptr的时候指定你想要的deleter。

shared_ptr

也是顾名思义,可以有多个shared_ptr指向同一个对象,只有在shared_ptr的RC为0的时候才会调用deleter。可以复制和拷贝。会对应的改变RC的值。

weak_ptr

这个指针存在的意义主要是为了解决两个对象互相持有对方的shared_ptr形成循环引用带来的内存泄漏 (A 持有 B的shared_ptr, B 持有 A的shared_ptr, RC永远不为0)。当使用weak_ptr的时候不会增加shared_ptr的RC,增加的是weak_ptr 的weak_RC。与unique_ptr和shared_ptr不同的是weak_ptr是不支持解引用的(因为指向的对象可能已经不存在了),需要调用lock方法返回对应的shared_ptr,如果shared_ptr不为空说明对象仍然存在才可以使用指向的对象。

鉴于现在有些公司面试还要手写智能指针,这里就研究一下智能指针的源码写一个demo出来。在这里我们写一个shared_ptr的demo。

首先我们先研究一下C++标准里咋写的这个玩意儿。这里截图用的xcode的llvm的libc++。

我们先看一下标准库里的shared_ptr到底存了啥。看一下他的成员变量。

主要是两个,第一个是智能指针包的裸指针,第二个是指向存储strong RC和weak RC值的一个结构体。再看看那个记录了RC的结构体里到底放了啥。

其实里面存的就是一个shared_ptr的strong ref和weak ref。有一点值得注意的是我们看源码可以看到里面对strong和weak的引用计数都是atomic的。因此在引用计数这个方面智能指针是线程安全的。但是你操作指针指向的对象的时候不是线程安全的哦。

现在我们知道了libc++中智能指针的实现方式。回想一下智能指针的需求我们实现起来大概就有思路了。首先持有一个裸指针和一个记录RC的结构体指针。在赋值和析构的时候对RC结构体里的数据进行修改即可。我们主要在这里写一个shared_ptr的类

RefCounter类(STL里的ctrl blk)

这个类主要是用来实现引用计数的相关操作。

class RefCounter{
public:
    RefCounter(){
        ref_cnt_ = 0;
    }
    void addRef(){
        ++ref_cnt_;
    }

    void decRef(){
        --ref_cnt_;
    }

    int getRef(){
        return ref_cnt_;
    }

private:
    atomic<int>ref_cnt_;
};

由于STL库里的是用原子操作来对引用计数进行增删的,这里也用atomic的RC

my_shared_ptr类

template <typename T>
class my_shared_ptr{
public:
    my_shared_ptr(T* ptr){
        ctrl_blk_ = new RefCounter();
        ctrl_blk_->addRef();
        ptr_ = ptr;
    }

    ~my_shared_ptr(){
        ctrl_blk_->decRef();
        if(ctrl_blk_->getRef() == 0){
            delete ptr_;
            delete ctrl_blk_;
        }
    }

    my_shared_ptr(const my_shared_ptr& rhs){
        ctrl_blk_ = rhs.ctrl_blk_;
        ptr_ = rhs.ptr_;
        ctrl_blk_->addRef();
    }

    my_shared_ptr& operator=(const my_shared_ptr& rhs){
        ctrl_blk_->decRef();
        if(ctrl_blk_->getRef() == 0){
            delete ctrl_blk_;
            delete ptr_;
        }
        ctrl_blk_ = rhs.ctrl_blk_;
        ctrl_blk_->addRef();
        ptr_ = rhs.ptr_;

        return *this;
    }

    RefCounter* ctrl_blk_;

private:
    T* ptr_;

};

测试代码

int main(){

    int* p = new int(1);
    {
        my_shared_ptr<int>ptr(p);
        assert(ptr.ctrl_blk_->getRef() == 1);
        {
            my_shared_ptr<int>ptr2(ptr);
            assert(ptr.ctrl_blk_->getRef() == 2);
        }
        assert(ptr.ctrl_blk_->getRef() == 1);
        int* p2 = new int(1);
        my_shared_ptr<int>ptr3(p2);
        ptr3 = ptr;
        assert(ptr.ctrl_blk_->getRef() == 2);

    }
    return 0;
}

常见面试题

1.智能指针的metadata存在哪里?是堆上还是栈上?

talk is cheap, show me the code. 下面是STL shared_ptr的构造函数的源码,里面用来统计引用计数的类。

可以看到相关数据是new出来的,也就是说是存在于堆上的。