Анимация
JavaScript
|
Главная Библионтека получает указатель на функцию. возвращает О, если работа выполнена успешно, или ненулевое значение, если возникла ошибка int atexit(void(*pFun)О); Компилятор генерирует функцию DestroySi ngl eton, разрушающую объект класса Singleton, хранящийся в буфере buffer, и передает ее адрес функции atexit. Как работает функция atexit? При каждом вызове этой функции ее параметр заталкивается в закрытый стек, предусмотренный библиотекой поддержки выполнения профамм на языке С (С mntime library). При выходе из приложения система поддержки выполнения профамм вызывает функции, зарегисфированные функцией atexit. Вскоре мы увидим, насколько важна функция atexit, а также (иногда, к сожалению), насколько она тесно связана с реализацией шаблона проектирования Singleton на языке С++. Нравится вам это или нет, она останется в центре внимания до конца главы. Независимо от того, каким образом реализуется удаление объекта класса Singleton, для его выполнения необходима функция atexit. Синглтоны Мейерса предоставляют простейшие средства для удаления объектов класса Singleton при выходе из профаммы. В большинстве случаев они работают безупречно. Мы хорошо их изучим, внесем в них некоторые усовершенствования и создадим альтернативные реализации для специальных ситуаций. 6.5. Проблема висячей ссылки Чтобы конкретизировать наше обсуждение, рассмофим пример, который мы будем в дальнейшем использовать для иллюсфации разных реализаций. Как и шаблон проектирования Si ngl eton, этот пример легко сформулировать и понять, но фудно реализовать. Допустим, что в нашем приложении используются три синглтона: Keyboard, Display и Log. У первых двух моделей есть физические прототипы. Синглтон Log предназначен для генерации сообщений об ошибках. Он может воплощаться в виде текстового файла либо вспомогательной консоли. Будем предполагать, что конструкция синглтона Log вынуждает затрачивать определенный объем дополнительных ресурсов, поэтому его лучше всего конкретизировать только в случае возникновения ошибки. Таким образом, если в профамме нет ошибок, она вообще не создает объект класса Log. Профамма пересылает объекту класса Log все ошибки, возникающие при конкретизации классов Keyboard и Display, а также при уничтожении их объектов. Если мы попытаемся реализовать эти классы в виде синглтонов Мейерса, профамма будет работать неправильно. Например, допустим, что после успешного создания объекта класса Keyboard инициализация объекта класса Display потерпела неудачу. Конструктор объекта класса Display создает объект класса Log, в нем регистрируется ошибка, и похоже, что на этом приложение должно завершить свою работу. При выходе из профаммы вступают в действие правила языка: система поддержки выполнения профамм разрушает локальные статические объекты в порядке, обратном очередности их создания. Следовательно, объект класса Log будет разрушен до объекта класса Keyboard. Если по некоторой причине объект класса Keyboard не будет успешно уничтожен, а попытается передать объекту класса Log сообщение о возникшей ошибке, функция-член Log::instance невольно вернет ссылку на "оболочку" разрушенного объекта класса Log. Таким образом, поведение профаммы станет непредсказуемым. В этом \\ заключается проблема висячей ссылки (dead-reference problem). Порядок создания и разрушения объектов классов Keyboard, Log и Display заранее не определен. Необходимо, чтобы классы Keyboard и Display подчинялись правилам языка С++ (последним создан - первым уничтожен), а класс Log должен быть исключением из этих правил. Независимо от того, когда он был создан, объект класса Log всегда должен уничтожаться после объектов классов Keyboard и Display, чтобы он мог получать сообшения об ошибках, возникаюших при их разрушении. Если приложение содержит несколько взаимодействуюших синглтонов, автоматически проконтролировать продолжительность их жизни невозможно. Нормальный синглтон должен по крайней мере распознавать висячие ссылки. Этого можно достичь, отслеживая процесс разрушения объектов с помошью статической булевской переменной destroyed . Начальное значение этой переменной равно false. Деструктор класса singleton присваивает ей значение true. Перед тем как перейти к реализации, подведем итоги. Кроме создания объекта класса singleton и возврата ссылки на него, функция-член Singleton: :instance должна распознавать висячую ссылку. Следуя принципу "одна функция - одна задача", реализуем три разные функции-члена: Create, фактически создающую объект класса singleton, OnDeadReference, выполняющую обработку ошибок, и хорошо известную функцию instance, предоставляющую доступ к объекту класса Singleton. Из всех этих функций только instance является открытой. Реализуем класс singleton, распознающий висячую ссылку. Во-первых, добавим в класс Singleton статическую булевскую переменную-член destroyed.. Она предназначена для индикации висячей ссылки. Затем изменим деструктор класса Singleton, чтобы он присваивал переменной plnstance значение О, а переменной destroyed.- значение true. Новый класс и функция OnDeadReference имеют следуюший вид. Singleton.h class Singleton { public: Singleton& instanceO { if (!pinstance.) { проверка висячей ссылки if (destroyed.) OnDeadReferenceO; else { Первый вызов - инициализация CreateO; return pinstance.; private: Создаем новый объект класса Singleton и присваиваем указатель на него переменной pinstance. static void CreateO Задача: инициализировать переменную pinstance. static singleton theinstance; plnstance =&thelnstance; вызывается, если обнаружена висячая ссылка static void onDeadReferenceC) throw std::runtime errorC"o6нapyжeнa висячая ссылка"); virtual -singletonС) { plnstance = 0; destroyed. = true; данные Singleton plnstance ; bool destroyed.; ... заблокированные операторы ... Singleton.cpp Singleton* singleton::plnstance = 0; bool Singleton::destroyed = false; Этот код работает! В момент завершения работы приложения вызывается деструктор класса Singleton. Он присваивает переменной pinstance. значение О, а переменной destroyed.- значение true. Если какой-нибудь объект попытается получить доступ к уничтоженному синглтону, поток управления достигнет функции оп-DeadReference, которая сгенерирует исключительную ситуацию runtime.error. Это простое и эффективное решение, не требующее больших затрат ресурсов. 6.6. Проблема адресации висячей ссылки (I): феникс Если описанное выше решение применить к проблеме KDL (Keyboard, Display, Log), результат окажется неутешительным. Если деструктору класса Display понадобится сообщить об ошибке уничтоженному объекту класса Log, функция-член Log::instance сгенерирует исключительную ситуацию. Если раньше поведение приложения было непредсказуемым, то теперь оно стало неудовлетворительным. Нам нужно, чтобы доступ к объекту класса Log был постоянным, независимо от того, когда он был создан. В крайнем случае можно было бы создать объект класса Log вновь (после его уничтожения), чтобы получить возможность использовать его для генерации сообщения об ошибках в любое время. Эта идея положена в основу шаблона проектирования Phoenix Singleton. Подобно легендарной птице Феникс, восстающей из пепла, такой синглтон может возникнуть вновь после своего уничтожения. В каждый фиксированный момент времени по-прежнему существует лишь один экземпляр класса singleton (двух синглтонов одновременно быть не может). Тем не менее при обнаружении висячей ссылки объект этого класса можно создавать вновь. Используя шаблон проектирования Phoenix Singleton, можно легко решить проблему KDL: классы Keyboard и Display остаются обычными синглтонами, а класс Log становится фениксом. Феникс можно легко реализовать с помощью статической переменной. При обнаружении висячей ссылки новый объект класса singleton создается под оболочкой старого. (В языке С++ это возможно. Память, выделенная для статических объектов, остается доступной на всем протяжении работы профаммы.) Разрушение нового объекта также регистрируется функцией atexit. Изменять функцию instance не нужно. Все модификации касаются только функции onDearReference. 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 |