- Любовные романы
- Фантастика и фэнтези
- Ненаучная фантастика
- Ироническое фэнтези
- Научная Фантастика
- Фэнтези
- Ужасы и Мистика
- Боевая фантастика
- Альтернативная история
- Космическая фантастика
- Попаданцы
- Юмористическая фантастика
- Героическая фантастика
- Детективная фантастика
- Социально-психологическая
- Боевое фэнтези
- Русское фэнтези
- Киберпанк
- Романтическая фантастика
- Городская фантастика
- Технофэнтези
- Мистика
- Разная фантастика
- Иностранное фэнтези
- Историческое фэнтези
- LitRPG
- Эпическая фантастика
- Зарубежная фантастика
- Городское фентези
- Космоопера
- Разное фэнтези
- Книги магов
- Любовное фэнтези
- Постапокалипсис
- Бизнес
- Историческая фантастика
- Социально-философская фантастика
- Сказочная фантастика
- Стимпанк
- Романтическое фэнтези
- Ироническая фантастика
- Детективы и Триллеры
- Проза
- Юмор
- Феерия
- Новелла
- Русская классическая проза
- Современная проза
- Повести
- Контркультура
- Русская современная проза
- Историческая проза
- Проза
- Классическая проза
- Советская классическая проза
- О войне
- Зарубежная современная проза
- Рассказы
- Зарубежная классика
- Очерки
- Антисоветская литература
- Магический реализм
- Разное
- Сентиментальная проза
- Афоризмы
- Эссе
- Эпистолярная проза
- Семейный роман/Семейная сага
- Поэзия, Драматургия
- Приключения
- Детская литература
- Загадки
- Книга-игра
- Детская проза
- Детские приключения
- Сказка
- Прочая детская литература
- Детская фантастика
- Детские стихи
- Детская образовательная литература
- Детские остросюжетные
- Учебная литература
- Зарубежные детские книги
- Детский фольклор
- Буквари
- Книги для подростков
- Школьные учебники
- Внеклассное чтение
- Книги для дошкольников
- Детская познавательная и развивающая литература
- Детские детективы
- Домоводство, Дом и семья
- Юмор
- Документальные книги
- Бизнес
- Работа с клиентами
- Тайм-менеджмент
- Кадровый менеджмент
- Экономика
- Менеджмент и кадры
- Управление, подбор персонала
- О бизнесе популярно
- Интернет-бизнес
- Личные финансы
- Делопроизводство, офис
- Маркетинг, PR, реклама
- Поиск работы
- Бизнес
- Банковское дело
- Малый бизнес
- Ценные бумаги и инвестиции
- Краткое содержание
- Бухучет и аудит
- Ораторское искусство / риторика
- Корпоративная культура, бизнес
- Финансы
- Государственное и муниципальное управление
- Менеджмент
- Зарубежная деловая литература
- Продажи
- Переговоры
- Личная эффективность
- Торговля
- Научные и научно-популярные книги
- Биофизика
- География
- Экология
- Биохимия
- Рефераты
- Культурология
- Техническая литература
- История
- Психология
- Медицина
- Прочая научная литература
- Юриспруденция
- Биология
- Политика
- Литературоведение
- Религиоведение
- Научпоп
- Психология, личное
- Математика
- Психотерапия
- Социология
- Воспитание детей, педагогика
- Языкознание
- Беременность, ожидание детей
- Транспорт, военная техника
- Детская психология
- Науки: разное
- Педагогика
- Зарубежная психология
- Иностранные языки
- Филология
- Радиотехника
- Деловая литература
- Физика
- Альтернативная медицина
- Химия
- Государство и право
- Обществознание
- Образовательная литература
- Учебники
- Зоология
- Архитектура
- Науки о космосе
- Ботаника
- Астрология
- Ветеринария
- История Европы
- География
- Зарубежная публицистика
- О животных
- Шпаргалки
- Разная литература
- Зарубежная литература о культуре и искусстве
- Пословицы, поговорки
- Боевые искусства
- Прочее
- Периодические издания
- Фанфик
- Военное
- Цитаты из афоризмов
- Гиды, путеводители
- Литература 19 века
- Зарубежная образовательная литература
- Военная история
- Кино
- Современная литература
- Военная техника, оружие
- Культура и искусство
- Музыка, музыканты
- Газеты и журналы
- Современная зарубежная литература
- Визуальные искусства
- Отраслевые издания
- Шахматы
- Недвижимость
- Великолепные истории
- Музыка, танцы
- Авто и ПДД
- Изобразительное искусство, фотография
- Истории из жизни
- Готические новеллы
- Начинающие авторы
- Спецслужбы
- Подростковая литература
- Зарубежная прикладная литература
- Религия и духовность
- Старинная литература
- Справочная литература
- Компьютеры и Интернет
- Блог
Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ - Скотт Майерс
Шрифт:
Интервал:
Закладка:
• Чтобы компенсировать субоптимальное выравнивание в распределителях по умолчанию. Как я уже упоминал, самый быстрый доступ к значениям double на архитектуре x86 получается тогда, когда они выровнены по восьмибайтным границам. К сожалению, операторы new, поставляемые с некоторыми компиляторами, не гарантируют восьмибайтового выравнивания при динамическом выделении double. В этих случаях замена оператора new по умолчанию на специальный, который гарантирует такое выравнивание, может дать заметный рост производительности программы.
• Чтобы сгруппировать взаимосвязанные объекты друг с другом. Если вы знаете, что определенные структуры данных обычно используются вместе, и хотите минимизировать частоту ошибок из-за отсутствия страницы в физической памяти при работе с такими данными, то, возможно, имеет смысл создать отдельную кучу для подобных структур, чтобы они были собраны вместе, или на столь небольшом числе страниц, насколько возможно. Версии операторов new и delete с размещением (см. правило 52) могут обеспечить такую группировку.
• Чтобы получить нестандартное поведение. Иногда может понадобиться, чтобы операторы new и delete делали нечто, чего поставляемые с компилятором версии делать не умеют. Например, вам нужно распределять и освобождать блоки памяти в разделяемой памяти, но для операций с такой памятью у вас только программный интерфейс C. Написание специальных версий new и delete (возможно, с размещением – см. правило 52) позволит вам обернуть C API в классы C++. Вы также можете написать специальный оператор delete, который заполняет освобождаемую память нулями, чтобы повысить степень защиты данных в приложении.
Что следует помнить• Есть много причин для написания специальных версий new и delete, включая повышение производительности, отладку ошибок при работе с кучей, а также сбор информации об использовании памяти.
Правило 51: Придерживайтесь принятых соглашений при написании new и delete
В правиле 50 объясняется, зачем могут понадобиться собственные версии операторов new и delete, но ничего не говорится о соглашениях, которых следует придерживаться при их написании. Следовать этим соглашениям не так уж сложно, но некоторые из них противоречат интуиции, поэтому знать о них необходимо.
Начнем с оператора new. От отвечающего стандарту оператора new требуется, чтобы он возвращал правильное значение, вызывал обработчика new, когда запрошенную память не удается выделить (см. правило 49), и правильно обрабатывал запросы на выделения нуля байтов. Кроме того, надо принять меры к тому, чтобы нечаянно не скрыть «нормальную» форму new, хотя это в большей мере касается интерфейса класса, чем требований реализации (см. правило 52).
Обеспечить правильность возвращаемого оператором new значения легко. Если вы можете выделить запрошенную память, то возвращаете указатель на нее. Если не можете, то следуете рекомендациям из правила 49 и возбуждаете исключение типа bad_alloc.
Однако все не так просто, потому что оператор new пытается выделить память не один раз, и после каждой неудачи вызывает функцию-обработчик new. Предполагается, что это сможет что-то сделать для освобождения некоторого объема памяти. Только тогда, когда указатель на обработчик new равен нулю, оператор new возбуждает исключение.
Забавно, но C++ требует, чтобы оператор new возвращал корректный указатель даже тогда, когда запрошено 0 байтов памяти. (Такое странное поведение упрощает реализацию некоторых вещей в других местах языка.) С учетом этого случая псевдокод для оператора new (нечлена класса) выглядит так:
void *operator new(std::size_t size) throw(std::bad_alloc)
{ // ваш оператор new может принимать
using namespace std; // дополнительные параметры
if (size == 0) { // обработать запрос на 0 байтов,
size = 1; // считая, что нужно выделить 1 байт
}
while(true) {
попытка выделить size байтов;
if(выделить удалось)
return (указатель на память);
// выделить память не удалось; проверить, установлена ли
// функция-обработчик new (см. ниже)
new_handler globalHandler = set_new_handler(0);
set_new_handler(globalHandler);
if(globalHandler) (*globalHandler)();
else throw std::bad_alloc();
}
}
Трактовка запроса на 0 байтов так, как если бы запрашивался 1 байт, выглядит сомнительно, но это просто, это корректно, это работает, к тому же на сколько часто вы собираетесь запрашивать 0 байтов?
Вам также может не понравиться то место в псевдокоде, где указатель на функцию-обработчик устанавливается в нуль, а затем восстанавливается его прежнее значение. К сожалению, нет способа непосредственно получить указатель на текущий обработчик new, поэтому приходится вызывать set_new_handler, чтобы получить его текущее значение. Грубо, но тоже эффективно, по крайней мере, в однопоточной программе. В многопоточной среде, возможно, понадобится какой-то механизм синхронизации для безопасного манипулирования (глобальными) структурами данных, связанными с функцией-обработчиком new.
В правиле 49 отмечено, что оператор new содержит бесконечный цикл, и в приведенном выше коде этот цикл присутствует: «while(true)». Единственный способ выйти из цикла – успешно выделить память либо выполнить в функции-обработчике одно из описанных в правиле 49 действий: сделать доступной больше памяти, установить другой обработчик, убрать текущий обработчик, возбудить исключение типа, производного от bad_alloc, либо не возвращать управления вовсе. Теперь вам должно быть ясно, почему обработчик new должен вести себя подобным образом. Если он нарушит это соглашение, то цикл внутри оператора new никогда не завершится.
Многие не понимают, что функция-член operator new наследуется производными классами. Это может привести к некоторым интересным осложнениям. Заметьте, что в приведенном псевдокоде operator new производится попытка выделить size байтов (если только size не равно нулю). Естественно, ведь size – это аргумент, переданный функции. Однако, как объясняется в правиле 50, одной из причин написания специального менеджера памяти является оптимизация размещения объектов определенного класса, но не любых его подклассов. Иными словами, если в классе X определен оператор new, то предполагается, что он рассчитан на объекты размера sizeof(X) – ни больше, ни меньше. Из-за наследования, однако, появляется возможность вызвать оператор new базового класса, чтобы выделить память для объекта производного класса:
class Base {
public:
static void *operator new(std::size_t size) throw(std::bad_alloc);
...
};
class Derived: public Base // в подклассе не объявлен operator new
{...};
Derived *p = new Derived; // вызывается Base::operator new!
Если определенный в классе Base оператор new не был спроектирован с учетом этой проблемы (а такая вероятность есть), то для корректной работы в случае, когда поступил запрос на выделение памяти «неправильного» размера, лучше всего обратиться к стандартному оператору new:
void *Base::operator new(std::size_t) throw(std::bad_alloc)
{
if(size != sizeof(Base)) // если size «неправильный»
return ::operator new(size); // вызвать стандартный оператор new
// для обработки запроса
... // в противном случае обработать запрос
// здесь
}
Я слышу возгласы: «Подождите! Вы забыли проверить патологический случай с нулевым размером!» На самом деле нет. Проверка присутствует, просто она является частью сравнения с sizeof(Base). C++ иногда предъявляется странные требования, например все автономные объекты должны иметь ненулевой размер (см. правило 39). По определению, sizeof(Base) никогда не может вернуть нуль, поэтому если size равно нулю, то запрос будет переадесован::operator new, и обязанность правильно обработать запрос возлагается на него.
Если вы хотите управлять распределением памяти для массивов на уровне класса, то нужно будет реализовать оператор new[] – специально для массивов. (Эта функция обычно называется «new для массивов», потому что трудно представить, как надо произносить «operator new[]»). Если вы решите написать operator new[], то помните, что она должна лишь выделить блок неформатированной памяти. Вы не можете ничего делать с еще не существующими объектами в этом массиве. Фактически вы даже не можете определить, сколько объектов будет в этом массиве. Во-первых, вы не знаете размер объекта. А ведь из-за наследования может быть вызван оператор new[] базового класса для выделения памяти под массив объектов производного класса, которые обычно больше объектов базового класса. Поэтому вы не можете предполагать внутри Base::operator new[], что размер каждого объекта равен sizeof(Base), а значит, нельзя предполагать, что общее количество объектов в массиве будет равно (запрошенное число байmов)/sizeof(Base). Во-вторых, параметр size_t, переданный оператору new[], может соответствовать большему объему памяти, чем займут сами объекты, потому что, как объясняется в правиле 16, в динамически выделенных массивах может резервироваться место для хранения числа элементов массива.

