Умные указатели с "этим" в C++

Я работал над заменой необработанных указателей указателями с подсчетом ссылок, которые предоставляют только константную версию базового. Моя цель - уменьшить использование памяти (и время, затрачиваемое на ненужное создание и разрушение сложных объектов), не оказываясь в ситуации, когда любой код имеет доступ к памяти, которой он не владеет. Я знаю о проблеме циклических ссылок со счетчиком ссылок, но мой код никогда не должен создавать такую ​​ситуацию.

Требование константности работает, потому что я использую систему, в которой классы обычно не предоставляют неконстантных членов, и вместо того, чтобы изменять объект, вы должны вызвать для него метод, который возвращает новый объект, полученный в результате изменения. Это, вероятно, шаблон дизайна, но я не знаю его по имени.

Моя проблема возникла, когда у меня есть метод, который возвращает указатель на объект его типа, который иногда сам по себе. Ранее это выглядело примерно так:

Foo * Foo::GetAfterModification( const Modification & mod ) const
{
  if( ChangesAnything( mod ) )
  {
    Foo * asdf = new Foo;
    asdf.DoModification( mod );
    return asdf;
  }
  else
    return this;
}

Я не могу найти хороший способ сделать это возвращение умный указатель. Наивный подход был бы что-то вроде return CRcPtr< Foo >( this ), но это нарушает семантику владения, потому что то, что я возвращаю и кому раньше принадлежал объект, теперь каждый думает, что имеет право собственности, но не знает друг о друге. Единственной безопасной вещью будет return CRcPtr< Foo >( new Foo( *this ) ), но это противоречит моему намерению ограничить ненужное использование памяти.

Есть ли способ безопасно вернуть умный указатель без выделения дополнительной памяти? Я подозреваю, что нет. Если бы было, как бы это работало, если бы объект был размещен в стеке? Этот вопрос кажется связанным, но он не тот, потому что он может просто заставить свои функции принимать необработанные указатели в качестве параметров и потому, что он использует библиотеку boost.

Для справки, моя домашняя реализация интеллектуального указателя приведена ниже. Я уверен, что он мог бы быть более надежным, но он более переносим, ​​чем зависеть от повсеместного повышения или Tr1, и до этой проблемы он работал хорошо для меня.

template <class T>
class CRcPtr
{
public:
  explicit CRcPtr( T * p_pBaldPtr )
  {
    m_pInternal = p_pBaldPtr;
    m_iCount = new unsigned short( 1 );
  }

  CRcPtr( const CRcPtr & p_Other )
  { Acquire( p_Other ); }

  template <class U>
  explicit CRcPtr( const CRcPtr< U > & p_It )
  {
    m_pInternal = dynamic_cast< T * >( p_It.m_pInternal );
    if( m_pInternal )
    {
      m_iCount = p_It.m_iCount;
      (*m_iCount)++;
    }
    else
      m_iCount = new unsigned short( 1 );
  }

  ~CRcPtr()
  { Release(); }

  CRcPtr & operator=( const CRcPtr & p_Other )
  {
    Release();
    Acquire( p_Other );
  }

  const T & operator*() const
  { return *m_pInternal; }

  const T * operator->() const
  { return m_pInternal; }

  const T * get() const
  { return m_pInternal; }

private:
  void Release()
  {
    (*m_iCount)--;
    if( *m_iCount == 0 )
    {
      delete m_pInternal;
      delete m_iCount;
      m_pInternal = 0;
      m_iCount = 0;
    }
  }

  void Acquire( const CRcPtr & p_Other )
  {
    m_pInternal = p_Other.m_pInternal;
    m_iCount = p_Other.m_iCount;
    (*m_iCount)++;
  }

  template <class U>
  friend class CRcPtr;

  T * m_pInternal;
  unsigned short * m_iCount;
};

template <class U, class T>
CRcPtr< U > ref_cast( const CRcPtr< T > & p_It )
{ return CRcPtr< U >( p_It ); }

Редактировать: Спасибо за ответы. Я надеялся избежать использования boost или tr1, но я признаю, что не использовать хорошо протестированные библиотеки, как правило, неразумно. Я совершенно уверен, что то, что я реализовал, не похоже на std::auto_ptr, но скорее похоже на tr1::shared_ptr, за исключением того, что оно предоставляет только const-версию внутреннего указателя и не имеет некоторых функций в официальной версии. Я действительно хотел бы избежать навязчивой схемы, подобной той, которая предложена Джаном Паоло. Я знаю, что моя реализация не является поточно-ориентированной, но это однопоточное приложение.

3 ответа

Посмотрите на источник общих указателей Boost. Если вы извлекаете класс из enable_shared_from_this<T>Вы можете позвонить shared_from_this() функция-член, чтобы делать такие вещи.

Что касается семантики, то отличительной чертой неизменности является то, что вы можете безопасно обмениваться данными между объектами, потоками и не беспокоиться о том, что люди изменяют значения, от которых вы зависите. Вам не нужно беспокоиться о владении, пока ваша схема пересчета верна (я не читал вашу, но в любом случае использовал Boost).

По словам Ферруччо, вам нужна некоторая форма общего указателя. То есть тот, который может быть (безопасно!) Разделен между несколькими объектами. Один из способов сделать эту работу - извлечь все ваши объекты (по крайней мере, те, которые предназначены для использования с общим указателем) из класса, который реализует фактический счетчик ссылок. Таким образом, фактический указатель сам несет с собой текущий счетчик, и вы разделяете указатель между различными объектами, которые вам нравятся.

То, что у вас сейчас есть, очень похоже на std::auto_ptr, и вы сталкиваетесь с теми же проблемами с владением. Google, и вы должны найти полезную информацию.

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

Опять гугл твой друг здесь. Просто найдите информацию об общих указателях в C++, и вы найдете тонны информации.

Другие вопросы по тегам