Бьерн Страуструп.
Язык программирования С++
359
void your(set_handle s)
{
for (T* p = s->first(); p; p = s->next())
{
//
...
}
//
...
}
void user()
{
set_handle sl(new slist_set);
set_handle v(new vector_set v(100));
my(sl);
your(v);
my(v);
your(sl);
}
Если классы set и set_handle разрабатывались совместно,легко реализовать подсчет числа
создаваемых множеств:
class set {
friend class set_handle;
protected:
int
handle_count;
public:
virtual void insert(T*) = 0;
virtual void remove(T*) = 0;
virtual int is_member(T*) = 0;
virtual T* first() = 0;
virtual T* next() = 0;
set() : handle_count(0) { }
};
Чтобы подсчитать число объектов данного типа set, в
управляющем классе нужно увеличивать или
уменьшать значение счетчика set_handle:
class set_handle {
set*
rep;
public:
set* operator->() { return rep; }
set_handle(set*
pp)
: rep(pp) { pp->handle_count++; }
set_handle(const
set_handle&
r)
: rep(r.rep) { rep->handle_count++; }
set_handle& operator=(const set_handle& r)
{
rep->handle_count++;
if (--rep->handle_count == 0) delete rep;
rep
=
r.rep;
return
*this;
}
~set_handle()
{ if (--rep->handle_count == 0) delete rep; }
};
Если все обращения к классу set обязательно идут через set_handle, пользователь может не
беспокоиться о распределении памяти под объекты типа set.
На практике иногда приходится извлекать указатель на содержательную часть из управляющего класса
и пользоваться непосредственно им. Можно, например, передать такой указатель функции, которая
Бьерн Страуструп.
Язык программирования С++
360
ничего не знает об управляющем классе. Если
функция не уничтожает объект, на который она получила
указатель, и если она не сохраняет указатель для дальнейшего использования после возврата, никаких
ошибок быть не должно. Может оказаться полезным переключение управляющего класса на другую
содержательную часть:
class set_handle {
set*
rep;
public:
//
...
set* get_rep() { return rep; }
void
bind(set*
pp)
{
pp->handle_count++;
if (--rep->handle_count == 0) delete rep;
rep
=
pp;
}
};
Создание новых производных от set_handle классов обычно не имеет особого смысла, поскольку это -
конкретный тип без виртуальных функций. Другое дело - построить управляющий класс для семейства
классов, определяемых одним базовым. Полезным приемом будет создание производных от такого
управляющего класса. Этот прием можно применять как для узловых классов, так и для абстрактных
типов.
Естественно задавать управляющий класс как шаблон типа:
template
class handle {
T*
rep;
public:
T* operator->() { return rep; }
//
...
};
Но при таком подходе требуется взаимодействие между управляющим и "управляемым" классами.
Если управляющий и управляемые классы разрабатываются совместно, например, в процессе
создания библиотеки, то это может быть допустимо. Однако, существуют и другие решения ($$13.10).
За счет перегрузки операции -> управляющий класс получает возможность контроля и выполнения
каких-то операций при каждом обращении к объекту. Например, можно вести подсчет частоты
использования объектов через управляющий класс:
template
class Xhandle {
T*
rep;
int
count;
public:
T* operator->() { count++; return rep; }
//
...
};
Нужна более сложная техника, если требуется выполнять операции как перед, так и после обращения к
объекту. Например, может потребоваться множество с блокировкой при выполнении операций
добавления к множеству и удаления из него. Здесь, по сути, в управляющем классе приходится
дублировать интерфейс с объектами содержательной части:
class set_controller {
set*
rep;
//
...
public:
lock();
unlock();
virtual void insert(T* p)
{ lock(); rep->insert(p); unlock(); }
Бьерн Страуструп.
Язык программирования С++
361
virtual void remove(T* p)
{ lock(); rep->remove(p); unlock(); }
virtual int is_member(T* p)
{
return
rep->is_member(p);
}
virtual T* first() { return rep->first(); }
virtual T* next() { return rep->next(); }
//
...
};
Писать
функции-переходники для всего интерфейса утомительно (а значит могут появляться ошибки),
но не трудно и это не ухудшает характеристик программы.
Заметим, что не все
функции из set следует блокировать. Как показывает опыт автора, типичный
случай, когда операции до и после обращения к объекту надо выполнять не для всех, а только для
некоторых
функций-членов. Блокировка всех операций, как это
делается в мониторах некоторых
операционных систем, является избыточной и может существенно ухудшить параллельный режим
выполнения.
Переопределив все
функции интерфейса в управляющем классе, мы получили по сравнению с
приемом перегрузки операции ->, то преимущество, что теперь можно строить производные от
set_controller классы. К сожалению, мы можем потерять и некоторые достоинства управляющего класса,
если к производным классам будут добавляться члены, представляющие данные. Можно сказать, что
программный объем, который разделяется между управляемыми классами уменьшается по мере роста
программного объема управляющего класса.
Достарыңызбен бөлісу: