// форма теперь скрыта
Derived *pd = new Derived; // правильно, вызывается
// оператор new из Derived
В правиле 33 достаточно подробно рассмотрен этот вид сокрытия имен в классе, но при написании функций распределения памяти нужно помнить, что по умолчанию C++ представляет следующие формы оператора new в глобальной области видимости:
void operator new(std::size_t) throw(bad_alloc); // обычный new
void operator new(std::size_t, void*) throw(bad_alloc); // размещающий new
void operator new(std::size_t, // new, не возбуждающий
const std::nothrow_t&) throw(); // исключений –
// см. правило 49
Если вы объявляете любой оператор new в классе, то тем самым скрываете все эти стандартные формы. Убедитесь, что вы сделали их доступными в дополнение к любым специальным формам new, объявленным вами в классе, если только в ваши намерения не входит запретить использование этих форм пользователям класса. И для каждого оператора new, к которому вы даете доступ, должен быть также предоставлен соответствующий оператор delete. Если вы хотите, чтобы эти функции вели себя обычным образом, просто вызывайте соответствующие глобальные их версии из своих функций.
Самый простой способ – создать базовый класс, содержащий все нормальные формы new и delete:
class StandardNewDeleteForms {
public:
// нормальные new/delete
static void *operator new(std::size_t size) throw(bad_alloc)
{ return ::operator new(size);}
static void operator delete(void *pMemory) throw()
{ ::operator delete(pMemory);}
// размещающие new/delete
static void *operator new(std::size_t size, void *ptr) throw(bad_alloc)
{ return ::operator new(size, ptr);}
static void operator delete(void *pMemory, void *ptr) throw()
{ ::operator delete(pMemory, ptr);}
// не возбуждающие исключений new/delete
static void *operator new(std::size_t, const std::nothrow_t& nt) throw()
{ return ::operator new(size, nt)}
static void operator delete(void *pMemory, const std::nothrow_t&) throw()
{ ::operator delete(pMemory, nt);}
};
Пользователи, которые хотят пополнить свой арсенал специальными формами new, применяют наследование и using-объявления (см. правило 33), чтобы получить доступ к стандартным формам:
class Widget: public StandardNewDeleteForms { // наследование
public: // стандартных форм
using StandardNewDeleteForms::operator new; // сделать эти формы
using StandardNewDeleteForms::operator delete; // видимыми
static void *operator new(std::size_t size, // добавляется
std::ostream& logStream) // специальный
throw(bad_alloc); // размещающий new
static void operator delete(void *pMemory, // добавляется
std::ostream& logStream) // соответствующий
throw(); // размещающий delete
...
};
• Когда вы пишете размещающую версию оператора new, убедитесь, что не забыли о соответственном размещающем операторе delete. Если его не будет, то в вашей программе могут возникать тонкие, трудноуловимые утечки памяти.
• Объявляя размещающие версии new и delete, позаботьтесь о том, чтобы нечаянно не скрыть нормальных версий этих функций.
Глава 9
Разное
Несмотря на то что эта глава состоит всего из трех правил, все они очень важны.
В первом правиле подчеркивается, что предупреждения компилятора – не пустяк, на который можно не обращать внимания. По крайней мере, если вы хотите, чтобы ваши программы вели себя правильно. Во втором представлен обзор стандартной библиотеки C++, включая и новую функциональность, предложенную в отчете TR1. И наконец, в последнем правиле представлен обзор проекта Boost – возможно, наиболее важного Web-сайта, посвященного общим вопросам применения C++. Игнорируя советы, изложенные в этих правилах, писать эффективные программы на C++ как минимум нелегко.
Правило 53: Обращайте внимание на предупреждения компилятора
Многие программисты зачастую игнорируют предупреждения компилятора. В конце концов, если бы проблема была по-настоящему серьезной, компилятор выдал бы ошибку! Подобные рассуждения могут быть сравнительно безвредными при работе с какими-нибудь другими языками, но в отношении C++ можно поручиться, что создатели компиляторов точнее вас оценивают истинное положение дел. Например, ниже приведена ошибка, которую рано или поздно допускает каждый из нас:
class B {
public:
virtual void f() const;
};
class D: public B {
public:
virtual void f();
};
Предполагается, что функция D::f будет переопределять виртуальную функцию B::f, но ошибка состоит в следующем: в классе B функция-член f – константная, а в D она не объявляется как const. Один из известных мне компиляторов сообщает следующее:
warning: D::f() hides virtual B::f()
(предупреждение: D::f() скрывает virtual B::f())
Многие неопытные программисты, получив подобное сообщение, говорят себе: «Конечно, D::f скрывает B::f – так и должно быть!» Они неправы. Вот что пытается сказать компилятор: f, объявленная в B, не была объявлена повторно в D, а полностью спрятана (объяснение причины этого явления см. в правиле 33). Если оставить без внимания данное предупреждение, это почти наверняка приведет к ошибочному поведению программы, и, чтобы найти причину, потребуются долгие часы отладки – при том, что компилятор давно уже все обнаружил.
По мере того как вы приобретете опыт работы с предупреждениями конкретного компилятора, уже нетрудно будет понимать, что означают различные сообщения (к сожалению, нередко реальное значение сообщения кардинально отличается от