Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 [ 49 ] 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

ты, зарегистрированные функцией SetLongevi ty, удаляются из памяти с помощью оператора delete в порядке убывания продолжительности их жизни.

Функцию SetLongevi ty нельзя применять к объектам, продолжительность жизни ко-торь[х управляется компилятором, например, к обычным глобальным объектам и автоматическим объектам. Компилятор уже создал код для их уничтожения, и вызов функции SetLongevi ty для этих объектов может спровоцировать их повторное уничтожение. (Это никогда не приводит ни к чему хорошему.) Функция SetLongevi ty предназначена для объектов, созданньк только с помощью оператора new. Более того, применение функции SetLongevi ty к этим объектам отменяет вызов оператора delete для их удаления.

В качестве альтернативы можно создать объект для управления зависимостью, который контролировал бы зависимость между объектами. Класс DependencyManager мог бы содержать функцию SetDependence, как показано ниже.

class DependencyManager {

public:

template <typename T, typename u>

void SetDependencyCT* dependent, u& target);

Деструктор класса DependencyManager уничтожал бы объекты по порядку, перед этим разрушая зависимости между ними.

Проектное решение, основанное на применении класса DependencyManager, имеет большой недостаток - оба объекта должны существовать одновременно. Это значит, что при попытке установить зависимость между объектами классов Keyboard и Log, например, придется создать объект класса Log, даже если в дальнейшем он соверщенно не нужен.

Для того чтобы избежать этой ловушки, можно установить зависимость между объектами классов Keyboard и Log внутри конструктора класса Log. Однако это создает слишком сильную связь: объект класса Keyboard зависит от определения класса Log (поскольку он использует этот класс), а объект класса Log зависит от определения класса Keyboard (поскольку он задает зависимость между ними). Это нежелательное явление называется циклической зависимостью (circular dependency). Оно подробно обсуждается в главе 10.

Вернемся к парадигме продолжительности жизни. Поскольку функция SetLongev-ity должна осторожно работать с функцией atexit, нужно тщательно определить точную последовательность вызовов деструктора в следующей профамме.

class SomeClass{ ... }

int main С) {

Создаем объект и задаем его продолжительность жизни SomeClass* pObjl = new SomeClass; SetLongevityCpObjl, 5);

Создаем статический объект, продолжительность жизни которого подчиняется правилам языка С++ static SomeClass obj2;

Создаем другой объект и задаем еще большую продожительность его жизни SomeClass* pObj3 = new SomeClass; SetLongevityCpObj3, 6);

в каком порядке будут уничтожены эти объекты?

В функции main определены как объекты, продолжительность жизни которых задается программистом, так и объекты, подчиняющиеся правилам языка С++. Опреде-



лить разумный порядок уничтожения этих трех объектов довольно трудно, поскольку кроме функции atexit у нас нет никаких средств для манипулирования скрытым стеком, предусмотренным системой поддержки выполнения программ.

Тщательный анализ этих офаничений приводит к следующим проектным рещениям.

• Каждый вызов функции SetLongevity должен сопровождаться вызовом функции atexi t.

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

• Разрушение объектов, имеющих одинаковую продолжительность жизни, должно следовать правилу языка С++: последним создан - первым уничтожен.

В приведенном выше примере эти правила приводят к следующему порядку уничтожения объектов: *pObjl, obj2, *pObj3. Первый вызов функции SetLongevity сопровождается вызовом функции atexit для уничтожения объекта *pObj3, второй вызов, соответственно, заканчивается вызовом функции atexit для уничтожения объекта *pObjl.

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

6.8. Реализация синглтонов, имеющих заданную продолжительность жизни

Несмофя на то что спецификация функции SetLongevity завершена, реализация не закончена. Функция SetLongevity поддерживает скрытую очередь с приоритетами (priority queue), не связанную с недоступным стеком функции atexit. В свою очередь функция SetLongevity вызывает функцию atexit, всегда передавая ей один и тот же указатель на функцию, которая выталкивает из стека и удаляет один элемент. Все это довольно просто.

Проблема заключается в использовании очереди с приоритетами, которые устанавливаются в соответствии с продолжительностью жизни, передаваемой функции Set-Longevity в виде параметра. Для заданной продолжительности жизни очередь функционирует как стек. Уничтожение объектов, имеющих одинаковую продолжительность жизни, осуществляется по правилу "последним пришел, первым ушел". Несмотря на совпадение имен, мы не можем использовать стандартный класс std: :priority queue, поскольку он не устанавливает порядок следования элементов, имеющих одинаковый приоритет.

Элементы данной сфуктуры данных содержат указатели на тип LifetimeTracker. Их интерфейс состоит из виртуального десфуктора и оператора сравнения. В производных классах десфуктор должен замещаться. (Мы -вскоре увидим, какая функция Compare для этого подходит лучше всего.)

namespace Private {

class LifetimeTracker {

public:

LifetimeTrackerCunsigned int x) : longevity Cx) {}



virtual ~LifetimeTracker() = 0; friend inline bool CompareC

unsigned int longevity,

const LifetimeTracker* p) { return p->longevity > longevity; } private:

unsigned int longevity.;

необходимое определение

inline LifetimeTracker::~LifetimeTrackerC) {}

Очередь с приоритетами представляет собой динамический массив указателей на функции LifetimeTracker.

namespace Private {

typedef LifetimeTracker** TrackerArray; extern TrackerArray pTrackerArray; extern unsigned int elements;

В профамме может существовать лишь один экземпляр типа Tracker. Следовательно, при работе с массивом ртгаскегАггау возникают все те же проблемы, указанные выше для класса singleton. Возникает интересная проблема "курицы и яйца": функция SetLongevi ty должна быть и закрытой, и доступной одновременно. Чтобы решить эту проблему, функция SetLongevi ty использует для манипуляций с массивом ртгаскегАггау функции низкого уровня из семейства std:: mall ос (mall ос, real 1 ос и free)-". Таким образом, решение проблемы "курицы и яйца" перекладывается на механизм распределения динамической памяти в языке С, гарантирующий правильную работу приложения. Следует отметить, что реализация функции SetLongevi ty довольно проста. Она создает конкретный объект класса LifetimeTracker, добавляет его в стек и регистрирует вызов функции atexit.

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

Вспомогательная функция для удаления объектов template <typename т> struct Deleter

static void DeleteCT* pObj) { delete pObj; }

конкретный объект класса LifetimeTracker для объектов типа т

template <typename т, typename Destroyer>

class ConcreteLifetimeTracker : public LifetimeTracker

Фактически функция SetLongevi ty использует только функцию std: : real 1 ос, заменяющую собой и функцию mall ос, и функцию free. Если вызвать се с нулевым указателем, она ведет себя как функция std: : mall ОС, а если задать нулевой размер, она имитирует работу функции std: :f гее.



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 [ 49 ] 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105