簡體   English   中英

std :: tr1 :: shared_ptr如何實現?

[英]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是內部抽象類,其中包含:

  • 計數器(在復制分配/銷毀時增加/減少)
  • 使原子遞增/遞減所需的一切(如果有特定的平台原子INC / DEC則不需要)
  • 抽象的virtual destroy()=0;
  • 虛擬析構函數。

此類aux類(實際名稱取決於實現)是由一系列模板化類派生的(根據顯式構造函數給出的類型進行參數化,例如從T派生的U ),並添加:

  • 指向對象的指針(與T*相同,但具有實際的類型:需要正確管理T所有情況,以作為派生層次結構中具有多個T U的基礎)
  • 該副本deletor給出刪除策略的顯式構造的對象(或默認deletor只是在做刪除p ,其中pU*以上)
  • 銷毀方法的重寫,調用刪除器函子。

一個簡化的草圖可以是這樣的:

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. 在智能指針對象之間共享,其中每個智能指針都包含一個指向引用計數器的指針:

在此處輸入圖片說明

  1. 僅包含在額外結構中,該結構增加了pointe對象的間接訪問級別。 在此處,以較慢的訪問速度交換在每個智能指針中保留計數器的空間開銷:

在此處輸入圖片說明

  1. 包含在pointee對象本身內:侵入式引用計數。 缺點是必須先驗地構造對象,並具有用於計數的功能:

    在此處輸入圖片說明

  2. 最后,您問題中使用雙向鏈表進行引用計數的方法稱為引用鏈接,它是:

... [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_1shrd_ptr_2超出范圍,由於它們各自的ptr成員都指向彼此,因此不會回收它們正在管理的內存。 盡管這是這種內存周期的一個非常幼稚的示例,但是如果您不加約束地使用這些類型的指針,則它可能以一種更加有害且難以追蹤的方式發生。 例如,我可以看到在哪里嘗試實現一個循環鏈接列表,其中每個next指針都是std::shared_ptr ,如果您不太謹慎的話,可能會導致問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM