Bog'liq C -Eldjer-Djeff-for-Real-Programmers-RUS-www.itlibitum.ru
211 Недостаток такой методики очевиден — она слишком полагается на соблюдение всех правил
программистом. Можно сделать получше.
Укзатели с подсчетом ссылок
Давайте усовершенствуем базовый класс
RefCount
и создадим модифицированный шаблон умного
указателя для любых классов, производных от
RefCount
.
template class CP {
// “Указатель с подсчетом ссылок”
private:
Type*
pointee;
public:
CP(Type* p) : pointee(p) { pointee->Grab(); }
CP(const CP& cp) : pointee(cp.pointee)
{ pointee->Grab(); }
~CP() { ponintee->Release(); }
CP& operator=(const CP& cp)
{
if (this == &cp) return *this;
pointee->Release();
pointee = cp.pointee;
pointee->Grab();
return
*this;
}
Type* operator->() { return pointee; }
};
Если весь клиентский код будет обращаться к классам с подсчетом ссылок через этот или аналогичный
шаблон, подсчет ссылок осуществляется автоматически. При каждом создании новой копии указателя
происходит автоматический вызов
Grab()
. При каждом уничтожении указателя его деструктор
уменьшает значение счетчика. Единственная опасность заключается в том, что клиент обойдет умный
указатель. С этой проблемой можно справиться с помощью производящих функций целевого класса.
class Foo : public RefCount {
private:
Foo();
//
Вместе с другими конструкторами
public:
static CP make(); // Создаем экземпляр
//
Далее следует интерфейс Foo
};
Тем самым мы гарантируем, что доступ к
Foo
будет осуществляться только через указатель с
подсчетом ссылок. Обратите внимание: это не ведущий, а самый обычный умный указатель.
Ведущие указатели с подсчетом ссылок
Даже если вы не хотите модифицировать конкретный класс, чтобы сделать его производным от
RefCount
(например, если он имеет критические требования по быстродействию и объему или входит
в коммерческую библиотеку классов), не отчаивайтесь. Подсчет ссылок можно переместить в ведущий
указатель.
template class CMP {
// “Ведущий указатель с подсчетом ссылок”
private:
Type*
pointee;
unsigned long count;
212 public:
CMP() : pointee(new Type), count(0) {}
CMP(const CMP& cmp)
: pointee(new Type(*(cmp.pointee))), count(0) {}
~CMP() { delete pointee; }
// Независимо от счетчика
CMP& operator=(const CMP& cmp)
{
if (this == &cmp) return *this;
delete
pointee;
pointee = new Type(*(cmp.pointee));
return
*this;
}
Type* operator->() const { return pointee; }
void Grab() { count++; }
void
Release()
{
if (count > 0) count--;
if (count <= 0)
{
delete
pointee;
delete
this;
}
}
};
В сущности, это равносильно объединению старого шаблона ведущего указателя с базовым классом
RefCount
. Подсчет ссылок уже не выделяется в отдельный класс, но зато нам снова приходится
полагаться на правильное поведение программистов — существ, к сожалению, несовершенных.
Дескрипторы с подсчетом ссылок
На сцену выходит нечто новое: дескриптор (handle) с подсчетом ссылок. По отношению к шаблону
CMP
он станет тем же, чем
CP
был для
RefCount
, — то есть он автоматически вызывает функции
Grab()
и
Release()
в своих конструкторах, деструкторе и операторе
=
.
template class CH {
// “Дескриптор с подсчетом ссылок”
private:
CMP*
pointee;
public:
CH(CMP* p) : pointee(p) { pointee->Grab(); }
CH(const CH& ch) : pointee(ch.pointee) { pointee->Grab(); }
~CH() { pointee->Release(); }
CH& operator=(const CH& ch)
{
if (this == &ch) return *this;
if (pointee == ch.pointee) return *this;
pointee->Release();
pointee = ch.pointee;
pointee->Grab();
return
*this;
}