面试问题记录:c++中共享指针是怎样计数的?

共享指针,即多个指针指向同一个内存;

具体实现方式是采用的引用计数,即这块地址上每多一个指针指向他,计数加一;

为什么要采取引用计数呢?我是这样理解的:

因为有多个指针指向了同一个内存,如果提前释放了这个内存,那其他指向这个地址的指针则会成为野指针?如果所有的指针都不指向这个地址,这块地址还没被释放,就会被造成内存泄漏。。。所以采用计数的方式来判断这个地址上还有多少个指针,当最后一个指针不再指向这块内存的时候,就释放掉这块内存。

面试问题:这个计数是在哪计的,是每一个共享指针都有一个计数,还是一个内存有一个?。。。。。当然,按照上面的理解应该是每一块内存有一个计数器。那这个计数的变量储存在哪?

先看一下共享指针的一个简单实现:

template <class T>
class SharedPtr
{
public:
    SharedPtr(T *ptr) : _ptr(ptr), _pCount(new int(1)) {}
    SharedPtr(const SharedPtr<T> &ap) : _ptr(ap._ptr), _pCount(ap._pCount)
    {
        ++(*_pCount);
    }
    SharedPtr<T> &operator=(const SharedPtr<T> &ap)
    {
        if (_ptr != ap._ptr)
        {
            if (--(*_pCount) == 0)
            {
                delete _ptr;
                delete _pCount;
            }
            _ptr = ap._ptr;
            _pCount = ap._pCount;
            ++(*_pCount);
        }
        return *this;
    }
    T &operator*()
    {
        return *_ptr;
    }
    T *operator->()
    {
        return _ptr;
    }

protected:
    T *_ptr;
    int *_pCount;
};

实际共享指针封装了两个变量:

protected:
    T *_ptr;
    int *_pCount;

一个是普通指针,一个是计数的指针。也就是说,每个共享指针通过pCount这个指针找到他指向内存的指针计数;而这个计数变量在哪?

SharedPtr(T *ptr) : _ptr(ptr), _pCount(new int(1)) {}

可以看到,计数是在使用普通指针创建共享指针的时候初始化的。

    //使用共享指针创建共享指针
    SharedPtr(const SharedPtr<T> &ap) : _ptr(ap._ptr), _pCount(ap._pCount)
    {
        ++(*_pCount);
    }
    //使用共享指针对另一个共享指针赋值
    SharedPtr<T> &operator=(const SharedPtr<T> &ap)
    {
        if (_ptr != ap._ptr)
        {
            if (--(*_pCount) == 0)
            {
                delete _ptr;
                delete _pCount;
            }
            _ptr = ap._ptr;
            _pCount = ap._pCount;
            ++(*_pCount);
        }
        return *this;
    }

在使用共享指针创建共享指针时,都是在之前的引用计数上加一。

因此,答案就是:不是每个内存上存放一个引用计数,也不是每个共享指针存放一个引用计数。而是每次使用普通指针来创建共享指针的时候增加一个引用计数。。。。也就是说,同一个地址可以有多个不同引用计数。。。

例子:

用一个普通指针初始化两个共享指针:

#include <memory>
#include <iostream>

using namespace std;

int main()
{
    int *p = new int;
    *p = 5;

    shared_ptr<int> s_ptr(p);//s_ptr指向了这块地址,pCount = 1
    shared_ptr<int> s_ptr1 = s_ptr;//s_ptr1也指向了这块地址,pCount = 2
    shared_ptr<int> s_ptr2(p);//s_ptr2也指向了这块地址,不过重新创建了引用计数,pCount1 = 1

    cout<< " 初始化后的状态" <<endl;
    cout<<"p:"<<p<<"    value:"<<*p<<endl;
    cout<<"s_ptr :"<<s_ptr<<"  count:"<<s_ptr.use_count()<<"  value:"<<*s_ptr<<endl;
    cout<<"s_ptr1:"<<s_ptr1<<"  count:"<<s_ptr1.use_count()<< "  value:"<<*s_ptr1<<endl;
    cout<<"s_ptr2:"<<s_ptr2<<"  count:"<<s_ptr2.use_count()<<"  value:"<<*s_ptr2<<endl;
    return 0;
}

输出结果:

 初始化后的状态
p:0x1faa50    value:5
s_ptr :0x1faa50  count:2  value:5
s_ptr1:0x1faa50  count:2  value:5
s_ptr2:0x1faa50  count:1  value:5

可以看到s_ptr2的引用计数为1,不同于s_ptr1。

因此这样也存在风险:如果s_ptr2计数为0,则会执行delete p操作,释放掉内存。这样s_ptr等指针也就指向的内容也就释放了?

加一个例子试一试:

#include <memory>
#include <iostream>

using namespace std;

int main()
{
    int *p = new int;
    *p = 5;

    shared_ptr<int> s_ptr(p);//s_ptr指向了这块地址,pCount = 1
    shared_ptr<int> s_ptr1 = s_ptr;//s_ptr1也指向了这块地址,pCount = 2
    shared_ptr<int> s_ptr2(p);//s_ptr2也指向了这块地址,不过重新创建了引用计数,pCount1 = 1

    cout<< " 初始化后的状态" <<endl;
    cout<<"p:"<<p<<"    value:"<<*p<<endl;
    cout<<"s_ptr :"<<s_ptr<<"  count:"<<s_ptr.use_count()<<"  value:"<<*s_ptr<<endl;
    cout<<"s_ptr1:"<<s_ptr1<<"  count:"<<s_ptr1.use_count()<< "  value:"<<*s_ptr1<<endl;
    cout<<"s_ptr2:"<<s_ptr2<<"  count:"<<s_ptr2.use_count()<<"  value:"<<*s_ptr2<<endl;

    shared_ptr<int> s_ptr3(new int(10));
    s_ptr2 = s_ptr3;    //当前s_ptr2的引用计数为1,
                        //减少了这次使用引用计数变为0,
                        //s_ptr2执行 了 delete p;释放了p之前指向的内存
    cout<<endl;
    cout<<"========================================="<<endl;
    cout<<endl;
    cout<<" s_ptr2改变指向后的状态"<<endl;
    cout<<"p:"<<p<<"    value:"<<*p<<endl;
    cout<<"s_ptr :"<<s_ptr<<"  count:"<<s_ptr.use_count()<<"  value:"<<*s_ptr<<endl;
    cout<<"s_ptr1:"<<s_ptr1<<"  count:"<<s_ptr1.use_count()<< "  value:"<<*s_ptr1<<endl;
    cout<<"s_ptr2:"<<s_ptr2<<"  count:"<<s_ptr2.use_count()<<"  value:"<<*s_ptr2<<endl;
    return 0;
}

实际输出结果:

 初始化后的状态
p:0x10daa50    value:5
s_ptr :0x10daa50  count:2  value:5
s_ptr1:0x10daa50  count:2  value:5
s_ptr2:0x10daa50  count:1  value:5

=========================================

 s_ptr2改变指向后的状态
p:0x10daa50    value:17672824
s_ptr :0x10daa50  count:2  value:17672824
s_ptr1:0x10daa50  count:2  value:17672824
s_ptr2:0x10daa90  count:2  value:10

可以看见之前存储的变量值5确实被释放了。

发布了15 篇原创文章 · 获赞 18 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zhangruijerry/article/details/100927531