Бьерн Страуструп. Язык программирования С++ Второе дополненное издание



Pdf көрінісі
бет251/256
Дата11.07.2022
өлшемі2,87 Mb.
#37591
1   ...   248   249   250   251   252   253   254   255   256

часть представляется абстрактным классом. 
Теперь вернемся к абстрактному типу set из $$13.3. Как можно определить управляющий класс для 
этого типа, и какие это даст плюсы и минусы? Для данного класса set можно определить управляющий 
класс просто перегрузкой операции ->: 
class set_handle { 
set* 
rep; 
public: 
set* operator->() { return rep; } 
set_handler(set* pp) : rep(pp) { } 
}; 
Это не слишком влияет на работу с множествами, просто передаются объекты типа set_handle вместо 
объектов типа set& или set*, например: 
void my(set_handle s) 

for (T* p = s->first(); p; p = s->next()) 

// 
... 

// 
... 



Бьерн Страуструп.
Язык программирования С++ 
 
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 классы. К сожалению, мы можем потерять и некоторые достоинства управляющего класса, 
если к производным классам будут добавляться члены, представляющие данные. Можно сказать, что 
программный объем, который разделяется между управляемыми классами уменьшается по мере роста 
программного объема управляющего класса. 


Достарыңызбен бөлісу:
1   ...   248   249   250   251   252   253   254   255   256




©emirsaba.org 2024
әкімшілігінің қараңыз

    Басты бет