3. Если глобальный operator new в состоянии выделить достаточно памяти для объекта Widget, то operator new класса Widget возвращает указатель на выделенную память. Деструктор объекта, самостоятельно управляющего глобальным обработчиком new, автоматически восстанавливает тот глобальный обработчик, который был установлен перед вызовом operator new класса Widget.
Теперь посмотрим, как все это выразить на C++. Начнем с класса, управляющего ресурсами, который не содержит ничего, кроме основных операций, диктуемых идиомой RAII: захват ресурса во время конструирования объекта и освобождение при его уничтожении (см. правило 13):
class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh) // получить текущий
:handler(nh) {} // обработчик new
~NewHandlerHolder() // освободить его
{ std::set_new_handler(handler);}
private:
std::new_handler handler; // запомнить его
NewHandlerHolder(const NewHandlerHolder&); // предотвратить
NewHandlerHolder& // копирование
operator=(const NewHandlerHolder&); // (см. правило 14)
};
Это делает реализацию оператора new для Widget совсем простой:
void Widget::orerator new(std::size_td size) throw(std::bad_aloc)
{
NewHandlerHolder // установить обработчик
h(std::set_new_handler(currentHandler)); // new из класса Widget
return ::operator new(size); // выделить память или
// возбудить исключение
} // восстановить глобальный
// обработчик new
Пользователи класса Widget применяют эти средства следующим образом:
void outOfMem(); // объявление функции, которую нужно
// вызвать, если выделить память
// для Widget не удается
Widget::set_new_handler(outOfmem); // установка outOfMem в качестве
// обработчика new для Widget
Widget *pw1 = new Widget; // если выделить память не удалось,
// вызывается outOfMem
std::string *ps = new std::string; // если выделить память не удалось,
// вызывается глобальный обработчик new
// (если есть)
Widget::set_new_handler(0); // отменяет обработчик new
Widget *pw1 = new Widget; // если выделить память не удалось,
// сразу же возбуждается исключение (никакого
// обработчика new сейчас нет)
Код, реализующий эту схему, один и тот же (независимо от класса), поэтому разумно было бы повторно использовать его в других местах. Простой способ сделать это – создать «присоединяемый» базовый класс, то есть базовый класс, который предназначен для того, чтобы подклассы могли унаследовать одно-единственное средство, в данном случае способность устанавливать специфичный для класса обработчик new. Затем превратите базовый класс в шаблон, чтобы каждый производный класс мог получать разные копии данных.
При таком подходе принадлежащая базовому классу часть позволяет подклассам наследовать необходимые им функции set_new_handler и operator new, а шаблонная часть гарантирует, что у каждого подкласса будет собственный член данных currentHandler. Звучит сложновато, но код выглядит обнадеживающе знакомым. Фактически единственным отличием является то, что теперь он доступен любому классу:
template
class NewHandlerSupport { // поддержки специфичной для класса
public: // функции set_new_handler
static std::new_handler set_new_handler(std::new_handler p) throw();
static void *operator new(std::size_t size) throw(std::bad_alloc);
... // другие версии оператора new – см. правило 52
private:
static std::new_handler currentHandler;
};
template