Анимация
JavaScript
|
Главная Библионтека class Singleton { ... как и раньше ... void KillPhoenixSingletonC); добавлено void Singleton::OnDeadReference() { получаем оболочку уничтоженного синглтона CreateC); Теперь указатель plnstance ссылается на "пепел" синглтона - ячейки памяти, которые ранее занимал уничтоженный синглтон. На этом же месте создается новый синглтон newCplnstance ) Singleton; Ставим новый объект в очередь на уничтожение atexitCKillPhoenixSingleton); изменяем значение переменной destroyed., поскольку синглтон восстановлен destroyed. = false; void singleton::KillPhoenixSingletonC) { Снова все превращаем в пепел - вызываем деструктор явно. переменной pinstance. присваивается значение О, а переменной destroyed. - значение true plnstance ->~SingletonC); Оператор new, используемый в функции OnDeadReference, называется оператором размещения (placement new operator). Этот оператор не выделяет память, он лишь создает новый объект и размешает его по указанному адресу, в нашем случае - по адресу, указанному в переменной pinstance.. Интересное обсуждение этого оператора можно найти в книге (Meyers, 1998b). В приведенном выше описании класса Singleton добавлена новая функция Kill-PhoenixSingleton. Зная, что для возрождения феникса в программе используется оператор размешения new, компилятор больше не разрушает его, как обычную статическую переменную. Мы создаем его вручную, поэтому и уничтожать его должны сами, для чего и предназначен вызов функции atexitCKillPhoenixSingleton). Проанализируем поток событий. При выходе из приложения вызывается деструктор класса Singleton. Он устанавливает указатель на нулевой адрес и присваивает переменной destroyed, значение true. Предположим теперь, что к объекту класса Singleton вновь пытается получить доступ некий глобальный объект. Тогда функция instance вызовет функцию OnDeadReference, которая реанимирует объект класса Singleton и ставит в очередь вызов функции KillPhoenixSingleton. После этого функция Instance успешно возврашает ссылку на правильный объект класса singleton. Теперь цикл можно повторять снова. Феникс гарантирует, что глобальный объект и другие синглтоны в любое время могут обратиться к его правильному экземпляру. Это делает феникс привлекательным средством для создания надежных и всегда доступных объектов, таких как объект класса Log. Если сделать класс Log фениксом, программа всегда будет работать правильно. 6.6.1. Проблемы, связанные с функцией atexit Если сравнить код, приведенный в предыдущем разделе, с кодом из библиотеки Loki, обнаружится одно отличие. В библиотеке вызов функции atexit окружен директивой препроцессора #i f def. #ifdef atexit fixed Ставим новый объект в очередь на уничтожение atexi t(Ki11Phoeni xSi ngleton); #endif Если в программе нет директивы #define atexit fixed, вновь созданный феникс не будет уничтожен. Это приведет к утечке памяти, которой мы стремимся избежать. Это происходит потому, что в стандарте языка С++ есть досадный пробел. В нем не описано, что случится, если функция регистрируется с помощью функции atexi t во время вызова, который является следствием другой регистрации функции atexi t. Чтобы проиллюстрировать эту проблему, напищем короткую тестовую профамму. #include <cstdlib> void BarС) void fooС) std::atexitCBar); nt mainC) std::atexit(foo); Эта маленькая профамма регистрирует функцию Foo с помощью функции atexit. Функция atexit выполняет вызов atexit (ваг). Этот случай не описан ни в одном из стандартов языков С и С++. Поскольку функция atexit и разрушение статических переменных тесно связаны друг с другом, при выходе из приложения мы теряем почву под ногами. Стандарты языков С и С++ внутренне противоречивы. Они утверждают, что функция ваг будет вызвана до функции Foo, поскольку функция ваг зарегистрирована последней. Однако она не может вызываться первой, поскольку функция f00 уже была вызвана. Может показаться, что это слишком академическая проблема. Посмотрим на нее с другой стороны. Написанная выше профамма была проверена на трех широко распространенных компиляторах. Ее поведение изменялось от просто неправильного (утечка ресурсов) до полного краха. На поиск решения этой проблемы уйдет довольно много времени. В данный момент считается, что лучше всего в этой ситуации применять макросы. В других компиляторах Я обсуждал эту проблему как в группе новостей (comp.std.c++), так и по электронной почте со Стивом Кламажем (Steve Klamage), председателем комитета по стандартизации языка С++ (ANSI/ISO С++ Standards Committee). Эта проблема ему хорошо известна, и отчет об этом дефекте уже направлен в соответствующие комитеты. Их можно найти на Web-страниие littp: anubis.dkuug.dk/jtcl/sc22/wg21/docs/lwg-issues.litml#3. К счастью, наиболее приемлемым решением этой проблемы в настоящее время считается реализация класса Singleton, описанная здесь. Даже с функциями, вызываемыми в момент выхода из программы, функция atexi t работает по принципу стека, что и требуется. при вторичном создании феникса в конце концов произойдет утечка памяти. Если компилятор позволяет, можно вставить в программу директиву #def i ne ATEXIT FIXED перед директивой включения заголовочного файла Singleton.h. 6.7. Проблема адресации висячей ссылки (il): синглтон с заданной продолжительностью жизни Феникс удобен во многих ситуациях, но имеет массу недостатков. Он нарушает нормальный цикл жизни синглтона, что может создать проблемы для некоторых пользователей. В результате выполнения цикла разрушения-восстановления синглтона его состояние теряется. Программист, связанный с реализацией конкретного синглтона, используюший стратегию феникса (Phoenix strategy), должен уделить особое внимание хранению его состояния между моментами его разрушения и восстановления. Это особенно досадно, поскольку в ситуациях, подобных задаче KDL, нужно точно знать порядок действий. Если объект класса Log создан, он в любом случае должен быть разрушен после объектов классов Keyboard и Display. Иными словами, объект класса Log должен жить дольше, чем объекты классов Keyboard и Display. Нам нужен простой способ управления продолжительностью жизни разных синглтонов, и тогда мы сможем решить задачу KDL, задав для объекта классса Log более высокую продолжительность жизни, чем у объектов классов Keyboard и Display. Однако это еше не все! Описанная выше проблема относится не только к синглто-нам, но и к глобальным объектам вообше. Это позволяет ввести понятие управления продолжительностью жизни объектов (longevity control) независимо от концепции синглтона. Чем большую продолжительность жизни имеет объект, тем позже он будет уничтожен, причем неважно, является он синглтоном или глобальным объектом, размешенным в динамической памяти. класс Singleton class SomeSingleton { ... }; обычный класс Class SomeClass { ... } SomeClass* pGlobalObject(new SomeClass); int mainO { SetLongevityC&SomeSingletonO.InstanceC), 5); гарантирует, что объект pGlobalObject будет уничтожен после экземпляра класса SomeSirtgleton SetLongevityCpGlobal, 6); Функция SetLongevity получает ссылку на объект любого типа и целочисленное значение (продолжительность жизни). получает ссылку на объект, размещенный в памяти с помощью оператора new, и его продолжительность жизни template <typename т> void SetLongevityСт* pDynOject, unsigned int longevity); Функция SetLongevity гарантирует, что объект pDynObject переживет все объекты, имеюшие меньшую продолжительность жизни. При выходе из приложения все объек- 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 |