2012-07-02 2 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)); 

приводит к проблемам: *a является 0 вместо 13 и delete 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; 
    }; 
} 

Извините за большую часть исходного кода, но я действительно не знаю, как я мог бы сузить ее дальше. Я был бы признателен за любые идеи, которые могут вызывать segfault, и более того, почему это не происходит при использовании сброса вручную.

Update

Мой (не работоспособный) оператор присваивания:

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

«У меня есть статический зЬй :: Карта ref_track что отслеживает», что кажется плохой идеей, и ненужный – stijn

+0

оффтоп: 'шаблон 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, безусловно, неверна; это означает, что счетчик ссылок для старого объекта не будет уменьшаться. Просто 'if (this! = & Other) reset (other.get());' должен работать, пока работает странный подсчет ссылок. Сопоставление ссылок на карте слишком странно для меня, чтобы отлаживать в моей голове; лучше всего было бы пройти через него в отладчике и увидеть, когда вещи будут удалены. –

2

Похоже на нарушение rule of three: У вас есть собственный конструктор копирования и пользовательский деструктор, но не пользовательский оператор присваивания. Поэтому a = shared_ptr<int>(new int(13)) просто скопирует два указателя value_ и ptr_ из временного, без какого-либо обновления вашего отслеживания ссылок. Поэтому, когда вы уничтожаете временное, больше нет отслеживаемых ссылок на этот указатель, что приведет к его удалению. Также обратите внимание, что старый указатель будет просочился в задание.

0

вы забыли добавить оператор присваивания вашему классу-указателю, который должен уменьшать число ссылок на старый объект и увеличивать количество ссылок на назначенный объект. В большинстве случаев это самый простой способ реализовать оператор = с точки зрения копирования и функции свопинга:

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

    return *this; 
} 
Смежные вопросы