Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
Шрифт:
• Убрать обработчик new, то есть передать нулевой указатель set_new_handler. Если обработчик не установлен, то operator new сразу возбудит исключение при неудачной попытке выделить память.
• Возбудить исключение типа bad_alloc либо некоторого типа, унаследованного от bad_alloc. Такие исключения не перехватывает operator new, поэтому они распространяются до того места, где была запрошена память.
• Не возвращать управление – обычно вызвав abort или exit.
Эти варианты выбора обеспечивают вам достаточную гибкость в реализации функций-обработчиков new.
Иногда
С++ не поддерживает специфичных для класса обработчиков new, но он и не нуждается в них. Вы можете реализовать такое поведение самостоятельно. Для этого просто в каждом классе определяете собственную версию set_new_handler и operator new. Определенная в классе функция set_new_handler класса позволит пользователям задать обработчик new для класса (точно так же, как обычный set_new_handler устанавливает глобальный обработчик new). Принадлежащий классу operator new гарантирует, что при выделении памяти для объектов этого класса вместо глобального обработчика new будет использован тот, что определен в данном классе.
Предположим, вы хотите обработать ошибки выделения памяти для класса Widget. Понадобится функция, которая будет вызываться, когда operator new не может выделить достаточно памяти для объекта Widget, поэтому вы объявляете статический член типа new_handler для хранения указателя на обработчик new для класса. Тогда Widget будет выглядеть примерно так:
Статические члены класса должны быть определены вне самого класса (если только они не константные целые – см. правило 2), поэтому:
Функция set_new_handler в классе Widget сохранит переданный ей указатель и вернет тот указатель на функцию, действовавшую ранее. Так же поступает и стандартная версия set_new_handler:
А вот что должен делать operator new из класса Widget.
1. Вызвать стандартный set_new_handler, указав в качестве параметра функцию-обработчик ошибок из класса Widget. В результате обработчик new из класса Widget будет установлен в качестве глобального.
2. Вызвать глобальный operator new для реального выделения памяти. Если произойдет ошибка, глобальный operator new вызовет обработчик new, принадлежащий Widget, поскольку эта функция была установлена в качестве глобального обработчика. Если это ни к чему не приведет, то глобальный operator new возбудит исключение bad_alloc. В этом случае operator new из класса Widget должен восстановить исходный обработчик new, а затем распространить исключение. Чтобы гарантировать, что исходный обработчик всегда восстанавливается, класс Widget трактует его как ресурс и следует совету правила 13 об использовании управляющих ресурсами объектов для предотвращения утечек.
3. Если глобальный operator new в состоянии выделить достаточно памяти для объекта Widget, то operator new класса Widget возвращает указатель на выделенную память. Деструктор объекта, самостоятельно управляющего глобальным обработчиком new, автоматически восстанавливает тот глобальный обработчик, который был установлен перед вызовом operator new класса Widget.
Теперь посмотрим, как все это выразить на C++. Начнем с класса, управляющего ресурсами, который не содержит ничего, кроме основных операций, диктуемых идиомой RAII: захват ресурса во время конструирования объекта и освобождение при его уничтожении (см. правило 13):
Это делает реализацию оператора new для Widget совсем простой: