Умные указатели с «этим» в C ++ - PullRequest
2 голосов
/ 27 февраля 2009

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

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

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

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 ) ), но это противоречит моему намерению ограничить ненужное использование памяти.

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

Для справки, моя домашняя реализация умного указателя приведена ниже. Я уверен, что он может быть более надежным, но он более переносим, ​​чем в зависимости от повсеместного повышения или 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, за исключением того, что оно предоставляет только константную версию внутреннего указателя и не имеет некоторых Особенности в официальной версии. Я действительно хотел бы избежать навязчивой схемы, подобной той, которая предложена Джаном Паоло. Я знаю, что моя реализация не является поточно-ориентированной, но это однопоточное приложение.

Ответы [ 3 ]

5 голосов
/ 27 февраля 2009

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

0 голосов
/ 27 февраля 2009

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

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

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

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

0 голосов
/ 27 февраля 2009

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

...