[英]How is the std::tr1::shared_ptr implemented?
我一直在考慮使用共享指針,我知道如何自己實現—不想這樣做,所以我正在嘗試std::tr1::shared_ptr
,並且我有幾個問題...
參考計數如何實現? 它是否使用雙向鏈表? (順便說一句,我已經用谷歌搜索了,但是找不到可靠的東西。)
使用std::tr1::shared_ptr
有什么陷阱嗎?
shared_ptr
必須管理一個引用計數器,並帶有一個刪除器函子,該函子由初始化時給定的對象類型推導得出。
shared_ptr
類通常包含兩個成員: T*
(由operator->
返回,並在operator*
取消引用)和aux*
,其中aux
是內部抽象類,其中包含:
virtual destroy()=0;
此類aux
類(實際名稱取決於實現)是由一系列模板化類派生的(根據顯式構造函數給出的類型進行參數化,例如從T
派生的U
),並添加:
T*
相同,但具有實際的類型:需要正確管理T
所有情況,以作為派生層次結構中具有多個T
U
的基礎) deletor
給出刪除策略的顯式構造的對象(或默認deletor
只是在做刪除p
,其中p
為U*
以上) 一個簡化的草圖可以是這樣的:
template<class T>
class shared_ptr
{
struct aux
{
unsigned count;
aux() :count(1) {}
virtual void destroy()=0;
virtual ~aux() {} //must be polymorphic
};
template<class U, class Deleter>
struct auximpl: public aux
{
U* p;
Deleter d;
auximpl(U* pu, Deleter x) :p(pu), d(x) {}
virtual void destroy() { d(p); }
};
template<class U>
struct default_deleter
{
void operator()(U* p) const { delete p; }
};
aux* pa;
T* pt;
void inc() { if(pa) interlocked_inc(pa->count); }
void dec()
{
if(pa && !interlocked_dec(pa->count))
{ pa->destroy(); delete pa; }
}
public:
shared_ptr() :pa(), pt() {}
template<class U, class Deleter>
shared_ptr(U* pu, Deleter d) :pa(new auximpl<U,Deleter>(pu,d)), pt(pu) {}
template<class U>
explicit shared_ptr(U* pu) :pa(new auximpl<U,default_deleter<U> >(pu,default_deleter<U>())), pt(pu) {}
shared_ptr(const shared_ptr& s) :pa(s.pa), pt(s.pt) { inc(); }
template<class U>
shared_ptr(const shared_ptr<U>& s) :pa(s.pa), pt(s.pt) { inc(); }
~shared_ptr() { dec(); }
shared_ptr& operator=(const shared_ptr& s)
{
if(this!=&s)
{
dec();
pa = s.pa; pt=s.pt;
inc();
}
return *this;
}
T* operator->() const { return pt; }
T& operator*() const { return *pt; }
};
在要求weak_ptr
互操作性的地方,在aux
需要第二個計數器( weak_count
)(將由weak_ptr
遞增/遞減),並且只有在兩個計數器都達到零時才必須delete pa
。
參考計數如何實現?
使用基於策略的類設計 1可以將智能指針實現分解為:
儲存政策
所有權政策
轉換政策
檢查政策
作為模板參數包含在內。 流行的所有權策略包括:深復制,引用計數,引用鏈接和破壞性復制。
引用計數跟蹤指向(擁有2個 )同一對象的智能指針的數量。 當數字變為零時,pointe對象將被刪除3 。 實際計數器可能是:
... [1] 依賴於這樣的觀察,即您實際上並不需要指向一個pointe對象的智能指針對象的實際數量; 您只需要檢測該計數何時減少到零即可。 這導致保留“所有權列表”的想法:
引用鏈接優於引用計數的優點是,前者不使用額外的免費存儲,這使它更可靠:創建引用鏈接的智能指針不會失敗。 缺點是引用鏈接需要更多的內存來記賬(三個指針對一個指針加一個整數)。 另外,引用計數應該更快一些-復制智能指針時,只需要一個間接尋址和一個增量即可。 列表管理稍微復雜一些。 總之,僅當免費商店稀缺時,才應使用參考鏈接。 否則,更喜歡引用計數。
它(
std::shared_ptr
)是否使用雙向鏈表?
我在C ++標准中可以找到的全部是:
20.7.2.2.6 shared_ptr創建
...
7. [注意:這些函數通常會分配比sizeof(T)
更多的內存,以允許內部記賬結構(例如引用計數)。 —尾注]
我認為,其中不包含雙向鏈接列表,因為它們不包含實際計數。
使用
std::shared_ptr
有什么陷阱嗎?
計數管理或鏈接引用管理都是資源泄漏的受害者,這稱為循環引用 。 讓我們有一個對象A,它持有指向對象B的智能指針。此外,對象B還持有指向A的智能指針。這兩個對象形成循環引用; 即使您不再使用它們,它們也會互相使用。 引用管理策略無法檢測到此類循環引用,並且兩個對象將永遠分配。
因為shared_ptr
的實現使用引用計數,所以循環引用可能是一個問題。 可以通過更改代碼來斷開循環的shared_ptr
鏈,以使引用之一是weak_ptr
。 這是通過在共享指針和弱指針之間分配值來完成的,但是弱指針不會影響引用計數。 如果指向對象的唯一指針很弱,則該對象將被破壞。
1.如果制定為策略,則每個設計功能都具有多種實現方式。
2.智能指針類似於指向使用new
分配的對象的指針,不僅指向該對象,而且還負責其銷毀以及釋放它所占用的內存。
3.沒有其他問題,如果沒有使用其他原始指針和/或指向它。
[1]現代C ++設計:應用通用編程和設計模式。 Andrei Alexandrescu,2001年2月1日
如果要查看所有細節,可以看一下boost shared_ptr
實現:
https://github.com/boostorg/smart_ptr
引用計數似乎通常是通過計數器和平台特定的原子增量/減量指令或使用互斥鎖顯式鎖定(請參見detail命名空間中的atomic_count_*.hpp
文件)來實現。
使用
std::tr1::shared_ptr
有什么陷阱嗎?
是的,如果您在共享內存指針中創建周期,那么當最后一個指針超出范圍時,由智能指針管理的內存將不會被回收,因為仍然存在對該指針的引用(即,循環會導致引用計數)不降為零)。
例如:
struct A
{
std::shared_ptr<A> ptr;
};
std::shared_ptr<A> shrd_ptr_1 = std::make_shared(A());
std::shared_ptr<B> shrd_ptr_2 = std::make_shared(A());
shrd_ptr_1->ptr = shrd_ptr_2;
shrd_ptr_2->ptr = shrd_ptr_1;
現在,即使shrd_ptr_1
和shrd_ptr_2
超出范圍,由於它們各自的ptr
成員都指向彼此,因此不會回收它們正在管理的內存。 盡管這是這種內存周期的一個非常幼稚的示例,但是如果您不加約束地使用這些類型的指針,則它可能以一種更加有害且難以追蹤的方式發生。 例如,我可以看到在哪里嘗試實現一個循環鏈接列表,其中每個next
指針都是std::shared_ptr
,如果您不太謹慎的話,可能會導致問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.