А. Александреску - Современное проектирование на C++ (1119444), страница 39
Текст из файла (страница 39)
Однако она не может вызываться первой, поскольку функция яоо уже была вызвана. Может показаться, что это слишком академическая проблема. Посмотрим на нее с другой стороны. Написанная выше программа была проверена на трех широко распространенных компиляторах. Ее поведение изменялось от просто неправильного (утечка ресурсов) до полного краха.3 На поиск решения этой проблемы уйдет довольно много времени.
В данный момент считается, что лучше всего в этой ситуации применять макросы. В других компиляторах з Я обсуждал эту проблему как в группе новостей (совр.лы.с++), так и по электронной почте со Стивом Кламажем (Б(счс К)ашяйс), председателем комитета цо стандартизации языка С++ (А(ЧБ(/!БО С++ Б(аядагбл Солнц(цсе), Эта проблема ему хорошо известна, и отчет об этом дефекте уже направлен я соответствующие комитеты. Их можно найти на УЧ№Ь-странице Ьцр//апаша.бацай.б(л/)(с(/лс22/лчй2(/бося/)ий-(эшеяйит(№3. К счастью, наиболее приемлемым решением этой проблемы в настоящее время считается реализация класса Б(по1етоп, описанная здесь. Люке с функциями, вызываемыми а момент выхода из программы, функция атех(т работает по принципу стека, что и требуется.
Глава б, Реализация шаблона 8(пс(е(оп при вторичном создании феникса в конце концов произойдет утечка памяти. Если компилятор позволяет, можно всгаюпь в программу директиву Удей пе лтех11 р1хдо перед директивой включения заголовочного файла 5з пд1есоп.Ь. 6.7. Проблема адресации висячей ссылки (П): синглтон с заданной продолжительностью жизни Феникс удобен во многих ситуациях, но имеет массу недостатков. Он нарушает нормальный цикл жизни синглтона, что может создать проблемы для некоторых пользователей.
В результате выполнения цикла разрушения-восстановления синглтона его состояние теряется. Программист, связанный с реализацией конкретного синглтона, использующий стратегию феникса (РЬоеп(х а!та!еду), должен уделить особое внимание хранению его состояния между моментами его разрушения и восстановления. Зто особенно досадно, поскольку в ситуациях, подобных задаче К!)!., нужно точно знать порядок действий. Если объект класса код создан, он в любом случае должен быть разрушен после обьектов классов кеуЬоагд и озэр1ау. Иными словами, объект класса код должен жить дольше, чем объекты классов кеуЬоагд и озэр1ау. Нам нужен простой способ управления продолжительностью жизни разных синглтонов, и тогда мы сможем решить задачу КО!., задав для объекта клаесса сод более высокую продолжительность жизни, чем у объектов классов кеуЬоагд и оз эр1ау.
Однако это еще не все! Описанная выше проблема относится не только к синглтонам, но и к глобальным объектам вообще. Это позволяет ввести понятие управления продолжительностью жизни объектов ((олден!у сап!го!) независимо от концепции синглтона. Чем большую продолжительность жизни имеет объект, тем позже он будет уничтожен, причем неважно, является он синглтоном или глобальным объектом, размещенным в динамической памяти. // класс 5!пд1есоп с1аэв 5ове5зпд1еСоп ( ... ); // обычный класс С1аьэ 5овеС1аээ ( 5овеС1аээ* р61оЬа1ОЬ)есС(пем 5овеС1аээ); зпс вазпО 5еСЬопдеч!Су(бэовеэ!пд1есопО.1пэтапсеО, 5); // гарантирует, что объект ра1оЬа1оЬ)есс будет уничтожен // после экземпляра класса 5ове5зод1есоп 5етьопдеч(Су(р61оЬа1, б); ) Функция 5ессопдеч!Су получает ссылку на объект любого типа и целочисленное значение (продолжительность жизни).
// получает ссылку на объект, размещенный в памяти // с помощью оператора пеп, и его продолжительность жизни севр1асе <сурепаве т> чозд 5ессопдечзсу(т* роупо)есс, цпэ!дпед (пс 1опдеч(су); Функция 5ессопдеч!су гарантирует, что объект роупоь)есс переживет все объекты, имеющие меньшую продолжительность жизни, При выходе из приложения все объек- 162 Часть Рк Компоненты ты, зарегистрированные функцией 5есьопдеу1су, удаляются из памяти с помощью оператора де1есе в порядке убывания продолжительности их жизни. Функцию 5есьопдеч1су нельзя применять к объектам, продолжительность жизни которых управляется компилятором, например, к обычным глобальным объектам и автоматическим объектам. Компилятор уже создал код для их уничтожения, и вызов функции 5есг опдеу1су для этих объектов может спровоцировать их повторное уничтожение.
(Зто никогда не приводит ни к чему хорошему.) Функция 5етьопдеу1су предназначена для объектов, созданных только с помощью оператора пеи. Бачее того, применение функции 5ессопдеч1су к этим объектам отменяет вызов оператора де1есе для их удаления. В качестве альтернативы л~ожно создать объект для управления зависимостью, который контролировал бы зависимость между объектами.
Класс оерепдепсумапацег мог бы содержап функцию 5еСОерепдепсе, как показано ниже. с1аьз оерепдепсумападег рцЬ)чс: сешр1асе <сурепаше т, сурепаше ц> чо1д 5есоерепдепсу(т~ дерепдепс, цб сагцес); Деструктор класса оерепдепсумападег уничтожал бы объекты по порядку, перел этим разрушая зависимости между ними, Проектное решение, основанное на применении класса оерепдепсумападег, имеет большой недостаток — оба объекта должны существовать одновременно. Зто значит, что при попытке установить зависимость между объектами классов кеуЬоагд и сод, например, придется создать объект класса код, даже если в дальнейшем он совершенно не нужен.
Для того чтобы избежать этой ловушки, можно установить зависимость между объектами классов кеуЬоагд и сод внутри конструктора класса Сод. Однако это создает слишком сильную связь: объект класса кеуЬоагд зависит от определения кчасса сод (поскольку он использует этот класс), а объект класса Сод зависит от определения класса кеуЬоагд (поскольку он задает зависимость межау ними). Зто нежелательное явление называется циклической зависимостью (с)гсц1аг дерепдепсу).
Оно подробно обсуждается в главе ! О. Вернемся к парадигме продолжительности жизни. Поскольку функция 5етьопдемзсу должна осторожно работать с функцией асехзс, нужно тщательно определить точную последовательность вызовов деструктора в следующей программе. с1азз 5омеС)азз( ... ) чпс ма1п0 // Создаем объект и задаем его продолжительность жизни 5омеС1азз* РОЬ)1 = пеи 5ошеС1азэ; 5есьопдеч1су(роь)1, 5); // создаем статический объект, продолжительность жизни // которого подчиняется правилам языка С++ зсасзс 5омес1азз оЬ)2„ // Создаем другой объект и задаем еще большую // продожительность его жизни 5ОМЕС1авэя рОЬ)3 = ПЕИ 5ОШЕС1аээ; 5етсопдеу1су(рОЬ)3, 6); // в каком порядке будут уничтожены зти объекты7 В функции ша1п определены как объекты, продолжительность жизни которых задается программистом, так и объекты, подчиняющиеся правилам языка С++.
Опреле- 163 Глава б. Реализация шаблона Бйпд!езоп лить разумный порядок уничтожения этих трех объектов довольно трудно, поскольку кроме функции асехз с у нас нет никаких средств лля манипулирования скрытым стеком, предусмотренным системой подаержки выполнения программ. Тщательный анализ этих ограничений приводит к следующим проектным решениям. ° каждый вызов функции 5есьоодечзту должен сопровождаться вызовом функции асехзс.
° Уничтожение объектов, имеющих меньшую продолжительность жизни, должно предшествовать уничтожению объектов с большей продолжительностью жизни. ° Разрушение объектов, имеющих одинаковую продолжительность жизни, должно следовать правилу языка С++: последним создан — первым уничтожен. В приведенном выше примере эти правила приводят к следующему порядку уничтожения объектов: *роЬ)1, оЬ) 2, *роЬ) 3. Первый вызов функции 5есьопдечз ту сопровождается вызовом функции асехдс для уничтожения объекта *роЬ) 3, второй вызов, соответственно, заканчивается вызовом функции асехд с лля уничтожения объекта *роЬ) 1. Функция 5еСЬопдечзСу дает программистаМ возможность Управлять прололжительностью жизни объектов и хорошо согласуется с правилами языка С++, Заметим, однако, что, как и многие мощные инструменты, она может оказаться опасной. Следует придерживаться следующего правила: любой объект, использующий объект с заданной продолжительностью жизни, должен иметь меньшую продоллсительность жизни, чем используемый объект.
6.8. Реализация синглтонов, имеющих заданную продолжительность жизни несмотря на то что спецификация функции 5есьопдечдсу завершена, реализация не закончена. Функция 5еП.опдечз Су поддерживает скрытую очередь с лриоритетаии (рпоп)у диене), не связанную с недоступным стеком функции асехзс. В свою очередь функция 5ессопдечз су вызывает функцию асехз с, всегла передавая ей один и тот же указатель на функцию, которая выталкивает из стека и удаляет один элемент.
Все зто довольно просто. Проблема заключается в использовании очереди с приоритетами, которые устанавливаются в соответствии с продолжительностью жизни, передаваемой функции 5еС~опдечзту в виде параметра. Для заданной продолжительности жизни очередь функционирует как стек. Уничтожение объектов, имеющих одинаковую продолжительность жизни, осуществляется по правилу "последним пришел, первым ушел". Несмотря на совпадение имен, мы не можем использовать стандартный класс зсд:: ргз огд ту диене, поскольку он не устанавливает порядок следования элементов, имеющих одинаковый приоритет. Элементы данной структуры данных содерхсат указатели на тип ьзТесзветгасйег. Их интерфейс состоит из виртуального деструктора и оператора сравнения.
В производных классах деструктор должен замешаться. (Мы вскоре увидим, какая функция совраге для этого подходит лучше всего.) павезрасе ргзчасе ( с)ааа ьз1есзветгасКег ( риЬ)зс: ьзгесзветгасКег(ипзздпед зпс х): )опдечзсу (х) () Часть!1. Компоненты ч(гсца1 -ь(тестветгаскегО = О; тг(епд тп1(пе Ьоо1 совраге( цпэ(цпед 1пс 1опдечт'су, сопвс ьт'Еес1ветгас!сег* р) ( гесигп р->1опдеч(су > 1опдеч1су; ргтчасе: цпэз'дпед (пс 1опдеч(су ; ); // необходимое определение бп!(пе ьт'бетт'ветгаскег::-ьт'Тест'ветгас!тегО () Очередь с приоритетами представляет собой динамический массив указателей на функции стУест'ветгаскег.
павеэрасе рг1чате суредеб ст'Еесзветгас!сег*~ тгас!сеглггау! ехсегп тгаскеглггау ртгаскеглггау; ехсегп цпэ(дпед зпс е1евепсэ; В программе может существовать лищь один экземпляр типа тгаскег. Следовательно, при работе с массивом ртгас!сеглггау возникают все те же проблемы, указанные выше лля класса 5тпц1есоп. Возникает интересная проблема "'курицы и яйца": функция бетьопдечтСу должна быть и закрытой, и доступной одновременно. Чтобы решить эту проблему, функция 5етсопдеч(су использует для манипуляций с массивом ртгас!сегАггау функции низкого уровня из семейства зсд::ва11ос (ва11ос, геа11ос и аггее)'. Таким образом, решение проблемы "курицы и яйца" перекладывается на механизм распределения динамической памяти в языке С, гарантирующий правильную работу приложения.