46. При наличии пользовательского new следует предоставлять все стандартные типы этого оператора
46. При наличии пользовательского new следует предоставлять все стандартные типы этого оператора
Резюме
Если класс определяет любую перегрузку оператора new, он должен перегрузить все три стандартных типа этого оператора — обычный new, размещающий и не генерирующий исключений. Если этого не сделать, то эти операторы окажутся скрытыми и недоступными пользователям вашего класса.
Обсуждение
Обычно пользовательские операторы new и delete нужны очень редко, но если они все же оказываются необходимы, то вряд ли вы захотите, чтобы они скрывали встроенные сигнатуры.
В С++, после того как вы определите имя в области видимости (например, в области видимости класса), все такие же имена в охватывающих областях видимости окажутся скрыты (например, в базовых классах или охватывающих пространствах имен), так что перегрузка никогда не работает через границы областей видимости. Когда речь идет об имени оператора new, необходимо быть особенно осторожным и внимательным, чтобы не усложнять жизнь себе и пользователям вашего класса.
Пусть вы определили следующий оператор new, специфичный для класса:
class С {
// ...
// Скрывает три стандартных вида оператора new
static void* operator new(size_t, MemoryPool&);
};
Теперь, если кто-то попытается написать выражение с обычным стандартным new С, компилятор сообщит о том, что он не в состоянии найти обычный старый оператор new. Объявление перегрузки C::operator new с параметром типа MemoryPool скрывает все остальные перегрузки, включая знакомые встроенные глобальные версии, которые все мы знаем и любим:
void* operator new(std::size_t); // Обычный
void* operator new(std::size_t,
std::nothrow_t) throw(); // He генерирующий исключений
void* operator new(std::size_t,
void*); // Размещающий
В качестве другого варианта событий предположим, что ваш класс предоставляет некоторую специфичную для данного класса версию оператора new — одну из трех. В таком случае это объявление также скроет остальные две версии:
class С {
// ...
// Скрывает две другие стандартные версии оператора new
static void* operator new(size_t, void*);
};
Предпочтительно, чтобы у класса С в его область видимости были явно внесены все три стандартные версии оператора new. Обычно все они должны иметь одну и ту же видимость. (Видимость для отдельных версий может быть сделана закрытой, если вы хотите явно запретить один из вариантов оператора new, однако цель данной рекомендации — напомнить, чтобы вы не скрыли эти версии непреднамеренно.)
Заметим, что вы должны всегда избегать сокрытия размещающего new, поскольку он интенсивно используется контейнерами стандартной библиотеки.
Все, что осталось упомянуть, — это то, что внесение оператора new в область видимости может быть сделано двумя различными способами в двух разных ситуациях. Если базовый класс вашего класса также определяет оператор new, все, что вам надо, — "раскрыть" оператор new:
class С : public B { // ...
public:
using B::operator new;
};
В противном случае, если не имеется базового класса или в нем не определен оператор new, вы должны написать короткую пересылающую функцию (поскольку нельзя использовать using для внесения имен из глобальной области видимости):
class C { // ...
public:
static void* operator new(std::size_t s) {
return ::operator new(s);
}
static void* operator new(std::size_t s,
std::nothrow_t nt) throw() {
return ::operator new(s, nt);
}
static void* operator new(std::size_t s, void* p) {
return ::operator new(s, p);
}
};
Рассмотренная рекомендация применима также к версиям операторов для массивов — operator new[].
Избегайте вызова версии new(nothrow) в вашем коде, но тем не менее обеспечьте и ее, чтобы пользователи вашего класса не оказались в какой-то момент неприятно удивлены.
Ссылки
[Dewhurst03] §60 • [Sutter04] §22-23