共享指針,即多個指針指向同一個內存;
具體實現方式是採用的引用計數,即這塊地址上每多一個指針指向他,計數加一;
爲什麼要採取引用計數呢?我是這樣理解的:
因爲有多個指針指向了同一個內存,如果提前釋放了這個內存,那其他指向這個地址的指針則會成爲野指針?如果所有的指針都不指向這個地址,這塊地址還沒被釋放,就會被造成內存泄漏。。。所以採用計數的方式來判斷這個地址上還有多少個指針,當最後一個指針不再指向這塊內存的時候,就釋放掉這塊內存。
面試問題:這個計數是在哪計的,是每一個共享指針都有一個計數,還是一個內存有一個?。。。。。當然,按照上面的理解應該是每一塊內存有一個計數器。那這個計數的變量儲存在哪?
先看一下共享指針的一個簡單實現:
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確實被釋放了。