2014-12-09 73 views
4

我努力使自己實現shared_ptr。我遇到問題make_shared。的std::make_shared的主要特徵在於它分配計數器塊和對象在連續的存儲器塊。我如何做同樣的事情?Make_shared - 自己實現

我試圖做這樣的事情:

template<class T> 
class shared_ptr 
{ 
private: 
    class _ref_cntr 
    { 
    private: 
     long counter; 

    public: 
     _ref_cntr() : 
      counter(1) 
     { 
     } 

     void inc() 
     { 
      ++counter; 
     } 

     void dec() 
     { 
      if (counter == 0) 
      { 
       throw std::logic_error("already zero"); 
      } 

      --counter; 
     } 

     long use_count() const 
     { 
      return counter; 
     } 
    }; 

    template<class _T> 
    struct _object_and_block 
    { 
     _T object; 
     _ref_cntr cntr_block; 

     template<class ... Args> 
     _object_and_block(Args && ...args) : 
      object(args...) 
     { 
     } 
    }; 

    T* _obj_ptr; 
    _ref_cntr* _ref_counter; 

    void _check_delete_ptr() 
    { 
     if (_obj_ptr == nullptr) 
     { 
      return; 
     } 

     _ref_counter->dec(); 

     if (_ref_counter->use_count() == 0) 
     { 
      _delete_ptr(); 
     } 

     _obj_ptr = nullptr; 
     _ref_counter = nullptr; 
    } 

    void _delete_ptr() 
    { 
     delete _ref_counter; 
     delete _obj_ptr; 
    } 

    template<class _T, class ... Args> 
    friend shared_ptr<_T> make_shared(Args && ... args); 

public: 
    shared_ptr() : 
     _obj_ptr(nullptr), 
     _ref_counter(nullptr) 
    { 
    } 

    template<class _T> 
    explicit shared_ptr(_T* ptr) 
    { 
     _ref_counter = new counter_block(); 
     _obj_ptr = ptr; 
    } 

    template<class _T> 
    shared_ptr(const shared_ptr<_T> & other) 
    { 
     *this = other; 
    } 

    template<class _T> 
    shared_ptr<T> & operator=(const shared_ptr<_T> & other) 
    { 
     _obj_ptr = other._obj_ptr; 
     _ref_counter = other._ref_counter; 

     _ref_counter->inc(); 

     return *this; 
    } 

    ~shared_ptr() 
    { 
     _check_delete_ptr(); 
    } 

}; 

template<class T, class ... Args> 
shared_ptr<T> make_shared(Args && ... args) 
{ 
    shared_ptr<T> ptr; 
    auto tmp_object = new shared_ptr<T>::_object_and_block<T>(args...); 
    ptr._obj_ptr = &tmp_object->object; 
    ptr._ref_counter = &tmp_object->cntr_block; 

    return ptr; 
} 

但是當我刪除對象和計數器塊,無效的堆塊發生異常。

+3

圍繞共享指針管理的兩個強制性代碼片段是(a)引用計數算法,以及(b)實際的* delete *,這兩個選項都不包括在本文中。我們不介意讀者。發佈展示實際問題的[**完整MCVE **](http://stackoverflow.com/help/mcve)。聲明「當我刪除對象和計數器塊...」表明你正在刪除兩個從未被直接分配的東西(實際上唯一的直接分配tmp_object永遠不會被保留)。 – WhozCraig 2014-12-09 11:39:07

+0

@WhozCraig謝謝!我已更新帖子 – 2014-12-09 12:33:19

+2

您的實施缺少一些關鍵功能。如果添加它們,解決方案可能會簡單地顯示在現有代碼之外。缺少的東西:刪除器的類型擦除,存儲與託管對象無關的指針(也許更多,比如線程安全的引用計數,弱引用計數,..)'shared_ptr'是複雜的,並且花費很長時間來開發目前的形式。 – dyp 2014-12-09 12:50:01

回答

8

注: _Treserved name,你不能使用它自己的類型/變量/參數等

的名字的問題是在這裏:

void _delete_ptr() 
{ 
    delete _ref_counter; 
    delete _obj_ptr; 
} 

這是錯誤的,make_shared情況,因爲你沒有分配兩個單獨的對象。

Boost和GCC的shared_ptr中的make_shared採用的方法是使用新的派生類型的控制塊,其中包括基類中的引用計數併爲派生類型中的被管理對象添加存儲空間。如果你讓_ref_cntr負責通過虛擬函數刪除對象,那麼派生類型可以覆蓋該虛函數以做一些不同的事情(例如,只使用顯式析構函數調用來銷燬對象而不釋放存儲)。

如果你給_ref_cntr虛析構函數,然後delete _ref_counter將正確地破壞派生類型,因此它應該成爲這樣的:

void _delete_ptr() 
{ 
    _ref_counter->dispose(); 
    delete _ref_counter; 
} 

但如果你不打算增加weak_ptr支持則沒有必要分離的管理對象和控制塊的破壞,你可以有控制塊的析構函數一舉兩得:

void _delete_ptr() 
{ 
    delete _ref_counter; 
} 

您當前的設計無法支持導入的shared_ptr螞蟻屬性,這是該template<class Y> explicit shared_ptr(Y* ptr)構造函數必須記住原始類型的ptr,並調用該刪除,而不是_obj_ptr(已轉換爲T*)。看到文檔的noteboost::shared_ptr相應的構造函數。爲了使這項工作的_ref_cntr需要使用類型擦除存儲原始指針,從shared_ptr對象_obj_ptr分開,使_ref_cntr::dispose()可以刪除正確的值。設計中的這種變化也需要支持aliasing constructor

class _ref_cntr 
{ 
private: 
    long counter; 

public: 
    _ref_cntr() : 
     counter(1) 
    { 
    } 

    virtual ~_ref_cntr() { dispose(); } 

    void inc() 
    { 
     ++counter; 
    } 

    void dec() 
    { 
     if (counter == 0) 
     { 
      throw std::logic_error("already zero"); 
     } 

     --counter; 
    } 

    long use_count() const 
    { 
     return counter; 
    } 

    virtual void dispose() = 0; 
}; 

template<class Y> 
struct _ptr_and_block : _ref_cntr 
{ 
    Y* _ptr; 
    explicit _ptr_and_block(Y* p) : _ptr(p) { } 
    virtual void dispose() { delete _ptr; } 
}; 

template<class Y> 
struct _object_and_block : _ref_cntr 
{ 
    Y object; 

    template<class ... Args> 
    _object_and_block(Args && ...args) : 
     object(args...) 
    { 
    } 

    virtual void dispose() { /* no-op */ } 
}; 

採用這種設計,make_shared變爲:

template<class T, class ... Args> 
shared_ptr<T> make_shared(Args && ... args) 
{ 
    shared_ptr<T> ptr; 
    auto tmp_object = new shared_ptr<T>::_object_and_block<T>(args...); 
    ptr._obj_ptr = &tmp_object->object; 
    ptr._ref_counter = tmp_object; 

    return ptr; 
} 

所以_ref_counter指向分配控制塊,當你做delete _ref_counter這意味着你你有一個正確的匹配new/delete一對分配和取消分配同一個對象,而不是用new創建一個對象,然後嘗試delete兩個不同的對象。

要添加weak_ptr支持,你需要第二次計數添加到控制塊,呼叫轉移到dispose()出來的析構函數,因此它被稱爲當第一計數爲零(例如,在dec()),只有通話第二個計數變爲零時的析構函數。然後以線程安全的方式做所有這些都會增加很多微妙的複雜性,這將比解答需要更長的時間來解釋。

而且,您的實現,這部分是錯誤的,出現內存泄漏:

void _check_delete_ptr() 
{ 
    if (_obj_ptr == nullptr) 
    { 
     return; 
    } 

這是可能的構造函數shared_ptr空指針,例如shared_ptr<int>((int*)nullptr),在這種情況下,構造函數將分配一個控制塊,但由於_obj_ptr爲空,所以不會刪除控制塊。

+1

哇!太奇妙了。但我有一個關於weak_ptr支持的問題。當沒有'shared_ptr'時,我應該刪除_obj_ptr。但是,當仍然有一些'weak_ptr's,我仍然應該維護'_ref_cntr'對象,對吧?但是當這個'_ref_cntr'是'_object_and_block'時,我不能刪除_obj_ptr而不刪除_ref_cntr。 – 2014-12-09 14:08:12

+1

是的,沒錯。在GCC的實現中,我的'_object_and_block'的等價物沒有'Y'類型的成員,而是它有一個'std :: aligned_storage :: type'類型的原始內存緩衝區,然後它創建'Y'在緩衝區中使用新的位置,'dispose()'''reinterpret_cast (&buffer) - >〜Y()'來銷燬它。這允許'Y'對象的生命週期比包含它的'_object_and_block'的生命週期更早結束。 – 2014-12-09 14:25:21

+0

因此,即使在GCC中,只有當最後一個'weak_ptr'死亡時,對象的內存纔會釋放? – 2014-12-09 14:37:14