Skip to the content.

从C++的RAII理解智能指针的思路(二)


Contact me:

Blog -> https://cugtyt.github.io/blog/index
Email -> cugtyt@qq.com
GitHub -> Cugtyt@GitHub


上次我们谈到了unique_ptr的思路,那么shared_ptr呢?注意到逻辑上这两个是有差别的,unique_ptr只有一个指针管理内存,而share_ptr可以多个指针管理,那么我们必须取消掉Unique中不能复制的限制,那么多次释放内存的问题怎么解决呢?

答案是我们可以通过计数的方式,如果有新指针指向申请的内存,那么计数值加1,少一个指针则减1,如果为0那么我们就释放内存,因此我们可以这样做(注意到我们使用了指针存储,这是为了保证所有的Shared使用同一个计数值):

class Shared {
public:
    Shared(int n) {
        ptr = new int[n];
        count = new unsigned;
        *count = 1;
    }
    Shared(const Shared& s) {
        count = s.count;
        ptr = s.ptr;
        ++(*count);
    }
    Shared& operator=(const Shared&) {/*与上面函数一样*/}
    ~Shared() {
        --(*count);
        if (!*count) {
            delete[] ptr;
            delete count;
        }
    }
    // other func
private:
    unsigned *count;
    int *ptr;
};

int main() {
    Shared s1(10);
    Shared s2(s1);
}

这样一个最简陋最粗糙的实现就完成了,再次提醒,这只是模型为了理解,和c++内部实现并不一样,很多问题也没有解决,只是为了粗略了解原理。

Shared有什么问题呢,当我们创建Shared的时候,我们必须指定申请内存,也就是说指针和内存空间要建立联系,但是有的时候我们不希望此时建立联系,为什么?也许我们只想需要的时候申请,不需要的时候就不用申请,这样即使有指针,也可以释放内存,或者只是做个检查,看下这个内存是不是有人管理,别人可以释放指针,这里就充当辅助的角色。更重要的是,如果Shared出现循环引用,那么内存还是会泄露,例如A->B,B->A,这样二者的count永远不会为0,也就内存泄露了,如果我们让管理是weak的,也就是说指针虽然指向内存,但是不计数。此处没有体现出这个问题,因为这是一个最为简单的实现,我们可以设定初始时刻设置内存地址,这样就可能出现这个问题了。

那怎么实现呢?我上面的说明已经说的很清楚了,就是不增加计数,听起来好像没什么用处,注意通常Weak要和Shared结合使用,这样,我们需要做一下改动:

class Weak {
public:
    Weak(Shared& s) {
        ptr = u.ptr;
        count = u.count;
    }
    Weak(const Weak& w) {
        count = w.count;
        ptr = w.ptr;
    }
    Weak& operator=(const Weak&) {/*与上面函数一样*/}
    ~Weak() = default;
    bool alive() { /*检查Shared是否存活*/ }
    // other func
private:
    unsigned *count;
    int *ptr;
};

int main() {
    Shared s1(10);
	Weak w1(s1);
	Weak w2(w1);
}

我们使用Shared初始化Weak,然后保留Shared的信息(此处因为要访问Shared的private成员,因此需要设置Weak为Shared的友元类),通过一个alive函数检查对象是否存活,因为这只是个简单的例子,因此alive的代码没有实现,如果想要了解如何实现,要看源码细节,但是我们可以想象,因为可能要在Shared释放后查看Shared的状态,因此肯定还存在一块区域记录信息,此处过于细节我们略去。

好的,我们有了一个基本的认识,离实际的智能指针还有不小的距离,再次注意此处仅仅是个例子,细节需要看源码,而且我们没有考虑多线程情况下同步,Unique,Shared和Weak之间交互等问题,此处仅仅是希望对智能指针做了什么做一个粗略的分析。