Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Название:Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Автор:
- Жанр:
- Издательство:Литагент «ДМК»233a80b4-1212-102e-b479-a360f6b39df7
- Год:2006
- Город:Москва
- ISBN:5-94074-304-8
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ краткое содержание
Эта книга представляет собой перевод третьего издания американского бестселлера Effective C++ и является руководством по грамотному использованию языка C++. Она поможет сделать ваши программы более понятными, простыми в сопровождении и эффективными. Помимо материала, описывающего общую стратегию проектирования, книга включает в себя главы по программированию с применением шаблонов и по управлению ресурсами, а также множество советов, которые позволят усовершенствовать ваши программы и сделать работу более интересной и творческой. Книга также включает новый материал по принципам обработки исключений, паттернам проектирования и библиотечным средствам.
Издание ориентировано на программистов, знакомых с основами C++ и имеющих навыки его практического применения.
Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
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
std::new_handler
NewHandlerSupport::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
template
void *NewHandlerSupport::operator(std::size_t size)
throw(std::bad_alloc)
{
NewHandlerHolder h(std::set_new_handler(currentHandler);
return ::operator new(size);
}
// currentHandler в любом классе инициализируется значением null
template
std::new_handler NewHandlerSupport::currentHandler = 0;
С этим шаблоном класса добавление поддержки set_new_handler к Widget очень просто: Widget просто наследуется от NewHandlerSupport. (Это может показаться экстравагантным, но ниже я подробно объясню, что здесь происходит.)
class Widget: public NewHandlerSupport {
... // как раньше, но без декларации
}; // set_new_handler или operator new
Это все, что нужно сделать в классе Widget, чтобы предоставить специфичный для класса обработчик set_new_handler.
Но может быть, вас беспокоит тот факт, что Widget наследует классу New-HandlerSupport? Если так, то ваше беспокойство усилится, когда вы заметите, что NewHandlerSupport никогда не использует свой параметр типа T. Он не нуждается в нем. Единственное, что нам нужно, – это отдельная копия NewHandlerSupport, а точнее его статический член currentHandler для каждого класса, производного от NewHandlerSupport. Параметр шаблона T просто отличает один подкласс от другого. Механизм шаблонов автоматически генерирует копию currentHandler для каждого типа T, для которого он конкретизируется.
А что касается того, что Widget наследует шаблонному базовому классу, который принимает Widget как параметр типа, не пугайтесь, это только поначалу кажется непривычным. На практике это очень удобная техника, имеющая собственное название, которое отражает тот факт, что никому из тех, кто видит ее в первый раз, она не кажется естественной. А называется она курьезный рекурсивный шаблонный паттерн (curious recurring template pattern – CRTP). Честное слово.
Однажды я опубликовал статью, в которой писал, что лучше было бы это назвать ее «Сделай Это Для Меня», потому что Widget наследует NewHandler-Support и как бы говорит: «Я – Widget, и я хочу наследовать классу NewHandlerSupport для Widget». Никто не станет пользоваться предложенным мной названием (даже я сам), но если думать о CRTP как о способе сказать «сделай это для меня», то вам будет проще понять смысл наследование шаблону.
Шаблоны, подобные NewHandlerSupport, упрощают добавление специфичных для класса обработчиков new к любому классу, которому это нужно. Однако наследование присоединяемому классу приводит к множественному наследованию, и прежде чем вставать на этот путь, вам, возможно, стоит перечитать правило 40.
До 1993 года C++ требовал, чтобы оператор new возвращал нулевой указатель, если не мог выделить нужную память. Теперь же operator new в этом случае должен возбуждать исключение bad_alloc. Но огромный объем кода на C++ был написан до того, как компиляторы стали поддерживать новую спецификацию. Комитет по стандартизации C++ не хотел отметать весь код, основанный на сравнении с null, поэтому было решено предоставить альтернативные формы operator new, обеспечивающие традиционное поведение с возвращением null при неудаче. Эти формы известны под названием «nothrow» (не возбуждающие исключений), потому что в них используются объекты nothrow (определенные в заголовке ) в точке, где используется new:
class Widget {...};
Widget *pw1 = new Widget; // возбуждает bad_alloc, если
// выделить память не удалось
Интервал:
Закладка: