面試問題記錄: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確實被釋放了。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章