А. Александреску - Современное проектирование на C++ (1119444), страница 23
Текст из файла (страница 23)
рис. 3.2) и линейную (см. рис. 3.6). Линейная иерархия классов наиболее эффективна с точки зрения размера. Распределенная иерархия классов обладает полезным свойством: все конкретизации шаблона, определенного пользователем (передаваемого в качестве аргумента классу пепэсаттегнз егагсйу), представляют собой корни финального класса, как показано на рис. 3.2.
3.15. Краткое описание класса ТуреНа1 Заголовочвый файл: туре15 зт. Ь. Все сущности класса находятся в пространстве имен ьоИ:: тс Определен шаблонный класс туре15 эт<неад, та(1>, Создание списка типов; определены макросы от ттядьтзт 1 до ттядь15т 50. Количество принимаемых ими параметров указано в их имени, Пользователь может повысить верхний предел макросов (50). воейпе ттядь15т 51(т1, повторять вплоть до т51) х ттядь15т(т1, ттядьтдт(т2, ловгпорять вплоть до т51)> По соглашению первым элементом списков типов всегда является простой тип (не список типов), а хвост может быть либо списком типов, либо классом нц11туре.
В заголовочном файле определен набор примитивов, оперирующих со списками типов. По соглашению все эти примитивы возвращают результат в виде определения вложенного (внутреннего) открытого типа под названием яезц1т. Если результатом работы примитива является значение, оно имеет имя ча1це. Примитивы описаны в табл. 3.1.
Краткое описание шаблонного класса пепзсаттегН1 егагсбу: Часть!. Методы тенр!ате <с1аьв тьззт, сенр1ате <с1аьв> с1азз цпзт> с1азз пеп5саттегнзегагсйу; ° Шаблонный класс пеп5саттегизегагсйу порождает иерархию, конкретизируюшую класс цпзт для каждого типа, указанного в списке типов тьйзт. Конкретизация класса беп5саттегнзегагсйу прямо нли косвенно наследует свойства класса цпз т<т> для каждого типа т из списка типов тьй зт, ° Структура иерархии, порожденной классом аеп5саттегНзегагсйу, изображена на рис. 35Е ° Краткое описание шаблонного класса аепьз пеагнз егагсйу: тенр1ате <с1азз тьззт, тенр1аее <с1азз, с1авв> с1авв цпзт> с1азз аепьзпеагнйегагсйу; Таблица 3.1.
Статические алгоритмы работы со списками типов Имя примитива Описание примитива ьепйтЬ<ть(зт> туреат<тьззт, (дх> Вычисляет длину списка типов т1з зт Возврашает тип, занимаюший заданную позицию в списке типов тьз зт (считая от нуля). если индекс больше длины списка или равен ей, возникает ошибка компиляции турелтноп5тгзст<тьззт, зс(х> Возврашает тип, занимающий заданную позицию в списке типов тьз зт (считая от нуля). Если индекс больше длины списка нли равен ей, возврашается тп нц11туре Возврашает индекс первого вхождения типа т в список т1 з зт.
Если тип не найден, возвращает число — 1 Добавляет в список ть(зт новый тип или список типов тпдехо1<тьззт, т> лррепб<тьззт, т> дгазе<тьззт, т> Удаляет первое вхождение в список тьй зт типа т (если оно есть) егазел11<тьйзт, т> Удаляет все вхождения в список тьй вт типа т (если они есть) 97 Глава 3.
Списки типов ° Шаблонный класс аепьзпеагнзегагсйу порождает линейную иерархию, изображенную на рис. 3.6. ° Шаблонный класс бепЬзпеагизегагсйу конкретизирует класс цпзт, передавая каждый тип из списка типов ть)зт в качестве первого шаблонного параметра зтого класса. Обратите внимание: класс цпз'с должен созлаваться на основе второго шаблонного параметра с помошью открытого наследования. ° Перегруженные функции гз е1д обеспечивают доступ к узлам иерархии по типу и по индексу. ° Функция гзе1б<туре>(оЬ)) возвразцает ссылку на конкретизацию класса цпзт, соответствуюшую указанному типу туре.
° Функция гзе1д<зпдех>(оЬ)) возврашает ссылку на конкретизацию класса цпзт, соответствующую типу, позиция которого в списке типов задается пело- численной константой зпдех. Окончание табл. 3.1 Описание примитива Имя примитива пер1асед11<тьзат, т> моатпегзчед<тьззт, т> 98 Часть!. Методы нооир1)сатев<тьзвт, т> пер1асе<тьзвт, т> пегз чебтоггопт<тьз ах „т> Удаляет из списка Т11зт все дубликаты Заменяет первое вхождение в список тьз ах типа т (если оно есть) типом 0 Заменяет все вхождения в список тьз'зт типа т (если они есть) типом 0 Определяет наиболее глубоко вложенный тип, производный от типа т. Если такого типа нет, возврашается тип Т Передвигает наиболее глубоко вложенные производные типы в голову списка типов т1з ат РАЗМЕЩЕНИЕ В ПАМЯТИ НЕБОЛЬШИХ ОБЬЕКТОВ В этой главе обсуждаются вопросы разработки и реализации механизма быстрого распределения памяти (айосагог) для небольших объектов.
Использование таких распределителей позволяет компенсировать дополнительные затраты ресурсов, происходящие при динамическом размещении объектов. В разных местах библиотеки (.о)г1 используются очень маленькие объекты — их размер может не превышать нескольких байтов. В главе 5, посвященной обобщенным функторам, и главе 7, описывающей интеллектуальные указатели, маленькие объекты используются очень широко.
По разным причинам, среди которых наиболее важной явяяется их полиморфное поведение, эти маленькие объекты нельзя хранить в стеке. Для работы с динамической памятью в языке С++ есть два основных инструмента — операторы пап и де1ете. Проблема заключается в том, что эти операторы универсальны и неэффективно работают при динамическом размещении маленьких объектов.
Чтобы проиллюстрировать, насколько плохо они работают с маленькими объектами, заметим, что некоторые стандартные распределители динамической памяти иногда работают на порядок медленнее и занимают в два раза больше памяти, чем распределители, описанные в этой главе. "Источник всех бед — ранняя оптимизация", — заявил Дональд Кнут (РопаЫ Кп(г)з). С другой стороны, Лен Латтанци (ьеп (агшпх() утверждает, что "поздняя пессимизация не приволит ни к чему хорошему". Пессимизация только на один порядок во время выполнения программы таких основных объектов, как функторы, интеллектуальные указатели или строки, легко может угробить весь проект.
("Пессимизация" — генерирование програмгяы, которая заведомо хуже ее оптимизированного варианта. -- Прим. ред.) Выгоды, которые приносит дешевое и быстрое динамическое распределение памяти для маленьких объектов, могут быть огромными, поскольку она позволяет применять совершенные технологии, не опасаясь значительных потерь производительности. Зги рассуждения являются достаточно сильным побудительным мотивом для разработки способов оптимального размещения маленьких объектов в динамической памяти. Авторы многих книг, посвященных языку С++, например, Саттер (бцпег, 2000) и Мейере (Меуегз, 1998а), упоминают о полезности разработки своих собственных специализированных средств распределения памяти.
Мейере, описав какую-либо реализацию, оставляет некоторые детали "в виде упражнения для самостоятельной работы", а Саттер отсылает читателей к "учебникам по С++ или программированию вообще". Издание, которое вы держите в руках, не претендует на то, чтобы стать вашей настольной книгой. Однако в этой главе мы опустимсл на уровень "железа" и реализуем свой собственный распределитель памяти в соответствии со станлартом языка С++ в мельчайших деталях. В этой главе описаны нюансы, связанные с настройкой механизмов распределения памяти.
В ней рассмотрен сверхмошный механизм распрелеления памяти для маленьких объектов из библиотеки Ьо)г), рабочая лошадка для интеллектуальных указателей и обобшенных функторов. 4.1. Стандартный механизм распределения динамической памяти По неизвестным причинам стандартный механизм распределения динамической памяти в языке С++ работает крайне медленно.
Возможно, это связано с тем, что обычно он реализуется как тонкая оболочка вокруг распрелелителя динамической памяти языка С (ва11ос/геа11ос/бгее), который неэффективно работает с небольшими участками памяти. В программах, написанных на языке С, обычно не применяются идиомы, позволяющие многократно выделять и освобождать небольшие участки памяти.
Вместо этого программы на языке С размешают в памяти средние и большие объекты (сотни и тысячи байтов), поэтому работа функций ва11ос/бгее оптимизирована именно для них. Кроме этого, универсальность механизма распределения памяти в языке С++ стала причиной крайней неэффективности использования памяти, выделяемой для маленьких объектов. Стандартный распределитель управляет кучей, что часто вынуждает его занимать лополнительную память.
Обычно вспомогательная память занимает от 4 до 32 байт лля каждого блока, выделенно~о с помошью оператора пеи. При выделении блоков памяти размером 1024 байт дополнительные затрат незначительны (от 0,4 % до 3 %). Однако, если возникает необходимость разместить в памяти объекты размером 8 байт, дополнительные затраты памяти составят от 50% до 400%. Эти числа достаточно велики, чтобы заставить программиста задуматься о том, как ему разместить в памяти большое количество маленьких объектов, В языке С++ динамическое распределение памяти имеет большое значение.
Динамический полиморфизм часто ассоциируется именно с динамическим распределением памяти. Такие стратегии, как "идиома Рппр1е" (1)зе рппр!е !сйогп) (Бцг!ег, 2000), изначально ориентируются на распределение динамической, а не статической памяти. (Название идиомы представляет собой аббревиатуру выражения "а Ро)п!ег го гйе !МРЬЕгпепга!!оп с1азз" — "указатель на класс реализации". — Прим. ред.) Следовательно, низкая производительность стандартного механизма распределения памяти делает его камнем преткновения на пути создания эффективных программ на языке С++.