2012-07-02 40 views
2

爲了培訓的目的,我試圖編寫自己的智能指針,模仿std::shared_ptr。我有一個static std::map<void *, int> ref_track跟蹤是否仍有共享指針引用內存中的某個塊。自己的共享指針的析構函數中的分割錯誤

我的概念是這樣的:

template <typename PType> 
class shared_ptr 
{ 
    public: 
     shared_ptr() 
     : value_(nullptr), ptr_(nullptr) 
     {} 

     template <typename T> 
     explicit shared_ptr(T * ptr) 
     : shared_ptr() 
     { 
      reset(ptr); 
     } 

     template <typename T> 
     shared_ptr(shared_ptr<T> const & other) 
     : shared_ptr() 
     {  
      reset(other.get()); 
     } 

     ~shared_ptr() 
     { 
      reset(); 
     } 

     void reset() 
     { 
      if(value_) 
      { 
       delete value_; // Segmentation fault here! 
       value_ = 0; 
       ptr_ = 0; 
      } 
     } 

     template <typename T> 
     void reset(T * ptr) 
     { 
      reset(); 
      if(ptr) 
      { 
       value_ = new shared_ptr_internal::storage_impl< 
        T 
       >(ptr); 
       ptr_ = ptr; 
      } 
     } 

     PType * get() const 
     { 
      return ptr_; 
     } 

     typename shared_ptr_internal::ptr_trait<PType>::type operator *() 
     { 
      return *ptr_; 
     } 

    private: 
     shared_ptr_internal::storage_base * value_; 
     PType * ptr_; 
}; 

當運行我的測試套件,我注意到,

shared_ptr<int> a(new int(42)); 
a.reset(new int(13)); 

工作正常,但

shared_ptr<int> a(new int(42)); 
a = shared_ptr<int>(new int(13)); 

導致問題:*a0而不是13delete value_a的析構函數中發生分段錯誤時崩潰。我已經在源代碼中註釋了崩潰。

所使用的內部類是

namespace shared_ptr_internal 
{ 

    typedef std::map<void *, int> ref_tracker; 
    typedef std::map<void *, int>::iterator ref_tracker_iterator; 
    typedef std::pair<void *, int> ref_tracker_entry; 

    static ref_tracker ref_track; 

    struct storage_base 
    { 
     virtual ~storage_base() {} 
    }; 

    template <typename PType> 
    struct storage_impl : storage_base 
    { 
     storage_impl(PType * ptr) 
     : ptr_(ptr) 
     { 
      ref_tracker_iterator pos = ref_track.find(ptr); 
      if(pos == ref_track.end()) 
      { 
       ref_track.insert(
        ref_tracker_entry(ptr, 1) 
       ); 
      } 
      else 
      { 
       ++pos->second; 
      } 
     } 

     ~storage_impl() 
     { 
      ref_tracker_iterator pos = ref_track.find(ptr_); 
      if(pos->second == 1) 
      { 
       ref_track.erase(pos); 
       delete ptr_; 
      } 
      else 
      { 
       --pos->second; 
      } 
     } 

     private: 
      PType * ptr_; 
    }; 

    template <typename PType> 
    struct ptr_trait 
    { 
     typedef PType & type; 
    }; 

    template <> 
    struct ptr_trait<void> 
    { 
     typedef void type; 
    }; 
} 

對不起了大部分的源代碼,但是我真的不知道我怎麼能進一步縮小它。我會很感激任何可能導致段錯誤的想法,此外,爲什麼手動使用重置時不會發生這種情況。

更新

我的(非工作)賦值運算符:

template <typename T> 
shared_ptr<PType> & operator =(shared_ptr<T> const & other) 
{ 
    if(this != &other) 
    { 
     value_ = nullptr; 
     ptr_ = nullptr; 
     reset(other.get()); 
    } 
    return *this; 
} 
+1

您可以嘗試'gdb'或其他調試器進行調試。 – phoxis

+0

「我有一個靜態的std ::地圖 ref_track用於跟蹤」 這似乎是一個壞主意,並且不需要 – stijn

+0

Offtopic:'模板 的shared_ptr(shared_ptr的常量及其他) :shared_ptr的(){ 復位 ( other.get()); } '不應該編譯。你從另一個調用構造函數? – fritzone

回答

2

你錯過了一個賦值操作符。

這意味着,在下面的代碼:

a = shared_ptr<int>(new int(13)); 

創建臨時共享指針;那麼默認賦值操作符只需將指針複製到a而不釋放舊值或更新引用計數;然後臨時刪除該值,留下a懸掛指針。

+0

我試圖添加一個賦值運算符(我用我的方法更新了我的帖子以獲取正確的代碼格式),但它不能解決問題。我已經爲該函數添加了一個調試斷點,但是斷點從來沒有達到過。我究竟做錯了什麼? – nijansen

+0

@nijansen:將'value_'和'ptr_'設置爲null肯定是錯的;這意味着舊對象的引用計數不會遞減。只要'(this!=&other)reset(other.get());'應該工作,只要奇怪的引用計數工作。基於地圖的引用計數對我來說太奇怪了,無法在我的腦海中進行調試;最好的辦法是在調試器中逐步完成,並確切地看到什麼時候被刪除。 –

2

好像違反了rule of three:您有自定義副本構造函數和自定義析構函數,但沒有自定義賦值運算符。因此,a = shared_ptr<int>(new int(13))只會臨時複製兩個指針value_ptr_,而不會更新參考跟蹤。因此,在銷燬臨時文件時,不會再有對該指針的跟蹤引用,這會導致其被刪除。另外請注意,舊指針將在作業中泄漏。

0

你忘了給你的指針類添加一個賦值操作符,該賦值操作符應該減少對舊對象的引用次數,並增加對賦值對象的引用次數。大多數情況下,這是實現operator =根據拷貝和交換函數的最簡單的方法:

shared_ptr& shared_ptr<T>::operator=(shared_ptr<T> other) 
{ 
    other.swap(this); 

    return *this; 
}