матриц, по одной для каждого вызова operator*. Более того, независимые операции умножения порождают последовательность из четырех циклов по элементам матрицы. Но применение передовой шаблонной технологии, тесно связанной с TMP и получившей название шаблоны выражений (expression templates), позволяет избежать создания временных объектов и объединить циклы, причем все это без изменения приведенного выше пользовательского кода. В результате программа требует меньше памяти и выполняется значительно быстрее.

• Генерация специализированных реализаций паттернов проектирования. Паттерны проектирования, подобные Strategy (см. правило 35), Observer, Visitor и т. п., могут быть реализованы многими способами. Используя основанную на TMP технологию, называемую проектирование на основе политик (policy-based design), можно создавать шаблоны, представляющие независимые проектные решения («политики»), которые могут быть соответствующим образом скомбинированы для порождения реализаций паттернов с заданным поведением. Например, эта техника применялась для того, чтобы из нескольких шаблонов, реализующих различное поведение «интеллектуальных» указателей, породить (во время компиляции) любой из сотен разных типов «интеллектуальных» указателей. В результате обобщения, выходящего за рамки привычных программных конструкций, к примеру паттернов проектирования и «интеллектуальных» указателей, эта технология ложится в основу так называемого порождающего программирования (generative programming).

Технология TMP предназначена не для всех. Применяемый в ней синтаксис интуитивно не очевиден, а поддерживающий инструментарий не развит. (Отладчики для шаблонных метапрограмм? Ну насмешили, право!) Поскольку это вспомогательный язык, открытый сравнительно недавно, то применяемые в нем соглашения носят пока экспериментальный характер. Тем не менее повышение эффективности за счет переноса части со стадии исполнения на стадию компиляции может оказаться значительным, а возможность выразить поведение, которое трудно или невозможно реализовать во время исполнения, также весьма привлекательно.

Поддержка TMP растет. Вероятно, в следующей версии C++ будет реализована явная поддержка этой технологии, в TR1 это уже декларировано (см. правило 54). Начали появляться книги, посвященные этой теме, а информация о TMP в Internet становится все богаче. Видимо, TMP никогда не станет главным направлением развития, но для некоторых программистов (особенно разработчиков библиотек) она почти наверняка займет важное место.

Что следует помнить

• Метапрограммирование шаблонов позволяет перенести часть работы со стадии исполнения на стадию компиляции. За счет этого можно раньше обнаружить ошибки и повысить производительность программ.

• Технология TMP может быть использована для генерации кода на основе комбинации политик, а также чтобы предотвратить генерацию кода, некорректного для определенных типов данных.

Глава 8

Настройка new и delete

В наши дни, когда вычислительные среды снабжены встроенной поддержкой «сборки мусора» (как, например, Java и. NET), ручной подход C++ к управлению памятью может показаться несколько устаревшим. Однако многие разработчики, создающие требовательные к ресурсам прикладные программы, выбирают C++ именно потому, что он позволяет управлять памятью вручную. Такие разработчики изучают, как используется память в их программах, и разрабатывают собственные процедуры распределения и освобождения памяти с целью достичь максимально возможной производительности (с точки зрения как времени, так и потребления памяти) своих систем.

Для этого нужно понимать, как организованы процедуры управления памятью в C++. Именно этой теме и посвящена настоящая глава. Два основных компонента здесь – это процедура выделения и освобождения памяти (операторы new и delete), а вспомогательная роль отводится обработчику new – функции, которая вызывается, когда new не может удовлетворить запрос на выделение памяти.

С управлением памятью в многопоточной среде связаны дополнительные сложности, не возникающие в однопоточных системах, поскольку «куча» – это модифицируемый глобальный ресурс, доступ к которому должен быть синхронизирован. Во многих правилах из настоящей главы идет речь об использовании модифицируемых статических данных. Эта тема всегда настораживает программистов, разрабатывающих многопоточные программы. Без правильной синхронизации, отсутствия взаимных блокировок в алгоритмах и тщательного проектирования с целью предотвращения одновременного доступа, обращения к процедурам работы с памятью могут легко привести к повреждению структур данных, управляющих кучей. Вместо того чтобы постоянно напоминать вам об этой опасности, я говорю об этом только здесь и предполагаю, что вы будете помнить об этом при чтении остальной части главы.

Следует помнить и о том, что операторы new и delete применимы только к выделению и освобождению одиночных объектов. Память для массивов выделяет operator new[] и освобождает operator delete[] (в обоих случаях «[]» является частью имен функций). Если явно не оговорено противное, то все сказанное об операторах new и delete касается также new[] и delete[].

И наконец, отмечу, что в случае STL-контейнеров выделением памяти из кучи управляют объекты- распределители, ассоциированные с самими контейнерами, а не напрямую new и delete. В этой главе ничего не говорится о распределителях памяти в STL.

Правило 49: Разберитесь в поведении обработчика new

Когда оператор new не может удовлетворить запрос на выделение памяти, он возбуждает исключение. Когда-то он возвращал нулевой указатель, и некоторые старые компиляторы все еще так и поступают. Вы можете столкнуться с таким устаревшим поведением, но я отложу его обсуждение до конца правила.

Прежде чем возбудить исключение в ответ на невозможность удовлетворить запрос на выделение памяти, оператор new вызывает определенную пользователем функцию, называемую обработчиком new (new-handler). (На самом деле это не совсем так. Реальное поведение new несколько сложнее. Подробности описаны в правиле 51.) Чтобы задать функцию, обрабатывающую нехватку памяти, клиенты вызывают set_new_handler – стандартную библиотечную функцию, объявленную в заголовочном файле <new>:

namespace std {

typedef void (*new_handler)();

new_handler set_new_handler(new_handler p) throw();

}

Как видите, new_handler – это typedef для указателя на функцию, которая ничего не принимает и не возвращает, а set_new_handler – функция, которая принимает и возвращает new_handler (конструкция throw() в конце объявления set_new_handler – это спецификация исключения; по существу, она сообщает, что функция не возбуждает никаких исключений, хотя на самом деле все несколько интереснее; подробности см. в правиле 29).

Параметр set_new_handler – это указатель на функцию, которую operator new вызывает при невозможности выделить запрошенную память. Возвращаемое set_new_handler значение – указатель на функцию, которая использовалась для этой цели перед вызовом set_new_handler.

Используется set_new_handler следующим образом:

// функция, которая должна быть вызвана, если operator new

// не может выделить память

void outOfMem()

{

std::cerr << “Невозможно удовлетворить запрос на выделение памяти ”;

std::abort();

}

int main()

{

std::set_new_handler(outOfMem);

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату