Анимация
JavaScript
|
Главная Библионтека мере), между этими вариантами есть существенная разница. Переменная-член in-stance инициализируется динамически (с помощью вызова конструктора класса Singleton во время выполнения программы), в то время как переменная plnstance инициализируется статически (она относится к типу, не имеющему конструктора, и инициализируется статической константой). Компилятор выполняет статическую инициализацию до первого оператора компоновки (assembly operator) программы. (Обычно статическая инициализация соответствует файлу, содержащему выполняемую программу, т.е. загрузка - это инициализация.) С другой стороны, в языке С++ не определен порядок динамической инициализации объектов, принадлежащих разным транслируемым модулям (translation units), что приводит к многочисленным недоразумениям. (Прще говоря, транслируемый модуль - это компилируемый исходный файл.) Рассмотрим следующий код. SomeFile.cpfj #include "Singleton.h" int global = Singleton::lnstanceC)->DoSomethingC); В зависимости от порядка инициализации, выбранного компилятором для переменных instance и global, вызов функции-члена Singleton::instance может возвращать объект, который еще не создан. Это значит, что переменную instance, нельзя считать проинициализированной, если ее использует другой внешний объект. 6.3. Обеспечение уникальности синглтонов Обеспечить уникальность объекта класса Singleton можно несколькими способами. Некоторые из них мы уже применяли. Это закрытые конструктор по умолчанию и конструктор копирования. Их применение блокирует код, приведенный ниже. Singleton sneakyC*singleton::lnstanceC)); Ошибка! He позволяет создать копию sneaky объекта класса Singleton, возвращаемого функцией-членом instance Если не определить конструктор копирования, компилятор сам создаст открытый конструктор (Meyers, 1998а). Объявление явного конструктора копирования блокирует автоматическую генерацию, и размещение конструктора в разделе private вызовет ошибку на этапе компиляции. Ситуацию можно немного исправить, если позволить функции-члену Instance возвращать ссылку, а не указатель. Проблема, связанная с указателем, возвращаемым функцией-членом instance, заключается в том, что вызывающий код может попытаться удалить его с помощью оператора delete. Для того чтобы минимизировать вероятность этого события, безопаснее возвращать ссылку. внутри класса Singleton static singletons instanceC); Другая функция-член, генерируемая компилятором по умолчанию, представляет собой оператор присваивания. Уникальность объекта не связана с оператором присваивания непосредственно, однако одно из ее очевидных последствий заключается в том, что присваивать один объект другому нельзя, поскольку два объекта класса Singleton одновременно существовать не могут. Для объекта класса Singleton любое присваивание оказывается самоприсваиванием (self-assignment), не имеющим никакого смысла. Следовательно, следует заблокировать оператор присваивания (сделав его закрытым и никогда не реализуя). Последний уровень защиты создается закрытым деструктором. Эта мера предотвращает случайное удаление указателя на объект класса Singleton. Таким образом, интерфейс класса Si ngl eton принимает следующий вид. class Singleton { Singleton& instanceC); ... операции ... private: Si ngl eton О; Singleton(const Singleton&); Singleton* operator=(const Singleton*); -SingletonO; 6.4. Разрушение объектов класса Singleton Как указывалось выше, объекты класса Singleton создаются по требованию при первом вызове функции-члена instance, который определяет момент создания, но оставляет открытым вопрос об удалении объекта. Когда следует удалять созданный экземпляр, в книге Gamma et al. (1995) не указано, однако, по свидетельству Джона Влиссидеса {Pattern Hatching, 1998), это трудная задача. Действительно, если объект класса Singleton не удален, то это не означает утечки памяти (memory leak). Подобная проблема возникает, когда в памяти накапливаются данные, ссылки на которые утеряны. Ситуация, связанная с объектом класса singleton, совершенно иная. Здесь ничего не накапливается, и адрес объекта известен до самого конца работы приложения. Более того, все современные операционные системы сами выполняют полную очистку памяти при завершении программы. (Интересное обсуждение проблемы, что считать, а что не считать утечкой памяти, проведено в книге Effective С++ (Meyers, 1998а).) Однако утечка памяти все же имеет место, более того, происходит скрытая утечка ресурсов (insidious resourse leak). Конструктор класса Singleton может запросить неограниченное количество ресурсов: сетевые соединения, обработку системных мьютек-сов (OS-wide mutexes) и другие внугрипроцессные средства связи, ссылки на внепро-цессные (out-of-pr6cess) CORBA- или СОМ-объекты и т.п. Единственный способ избежать утечки ресурсов - удалить объект класса Singleton в момент завершения работы приложения. Вопрос заключается в том, как выбрать правильный момент для удаления объекта, чтобы не возникло обращения к уже удаленному объекту. Проще всего при удалении синглтона положиться на языковые механизмы. Например, приведенный ниже код демонстрирует другой подход к реализации синглтона. Вместо размещения объекта в динамической памяти и применения статического указателя, функция instance использует локальную статическую переменную (local static variable). Singleton* Singleton::lnstanceO { static Singleton obj; return obj; Эта простая и элегантная реализация была впервые опубликована Скоттом Мейерсом (Meyers, 1996, Item 26), поэтому мы будем называть такой класс синглтоном Мейерса. Синглтон Мейерса использует некоторые скрытые свойства компилятора. Статические объекты, определенные в теле функции, инициализируются, когда поток управления впервые проходит через ее определение. Не путайте статические переменные, которые инициализируются во время выполнения программы, с элементарными статическими переменными, которые инициализируются статическими константами. Рассмотрим следующий пример. int FunС) { static int x = 100; return ++x; В данном случае переменная х инициализируется до выполнения первого оператора программы. При первом вызове функции Fun известно лишь, что переменная х давным-давно равна 100. В противном случае, когда переменная инициализируется не константой периода компиляции, или статическая переменная является объектом, имеющим конструктор, локальная статическая переменная инициализируется во время выполнения профаммы при первом проходе потока управления через определение функции. Кроме того, компилятор так генерирует код, что после инициализации система поддержки выполнения профамм регистрирует переменную как предназначенную к удалению. Этот алгоритм можно выразить с помощью следующего псевдокода. (Переменные, имя которых содержит два подчеркивания, считаются скрытыми, т.е. они генерируются и управляются только компилятором.) Singleton& Singleton::instanceC) { функции, генерируемые компилятором extern void ConstructSingletonCvoid* memory); extern void DestroysingletonO; Переменные, генерируемые компилятором static bool initialized = false; Буфер, в котором хранится синглтон (предполагается, что буфер выровнен) static char buffer[sizeof(Singleton)]; if (! initialized) Первый вызов, создается объект вызывается функция Singleton::Singleton в буферной памяти buffer ConstruetSingleton( buffer) Регистрируется удаление atexit( DestroySingleton); initialized = true; return *reinterpret cast<Singleton *>( buffer); Сердцевиной этого кода является вызов стандартной функции atexit, позволяющей автоматически вызывать зарегистрированные ею функции при выходе из программы в порядке L1F0 (last in, first out - последним пришел, первым ушел). (По определению удаление объектов в языке С++ выполняется по принципу LIFO: объекты, созданные первыми, разрушаются последними. Разумеется, объекты, явно управляемые с помощью операторов new и delete, этому правилу не подчиняются.) Сигнатура функции atexi t имеет следующий вид. 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 |