Умные указатели с "этим" в 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++, и вы найдете тонны информации.