открытого интерфейса класса
то
сделайте ее не членом класса (и, при необходимости,
в случаях 1 и 2 - другом)
Если функция требует виртуального поведения,
то
добавьте виртуальную функцию-член для обеспечения
виртуального поведения, и реализуйте функцию-не член
с использованием этой виртуальной функции.
иначе
сделайте ее функцией-членом.
basic_string
чересчур монолитен и имеет 103 функции-члена, из которых 71 без потери эффективности можно сделать функциями, не являющимися ни членами, ни друзьями класса. Многие из них дублируют функциональность, уже имеющуюся в качестве алгоритма стандартной библиотеки, либо представляют собой алгоритмы, которые могли бы использоваться более широко, если бы не были спрятаны в классе basic_string
. (См. рекомендации 5 и 32, а также [Sutter04].)
Каждая перегрузка void* operator new(parms)
в классе должна сопровождаться соответствующей перегрузкой оператора void operator delete(void* , parms)
, где parms
— список типов дополнительных параметров (первый из которых всегда std::size_t
). То же относится и к операторам для массивов new[]
и delete[]
.
Обычно редко требуется обеспечить наличие пользовательских операторов new
или delete
, но если все же требуется один из них — то обычно требуются они оба. Если вы определяете специфичный для данного класса оператор T::operator new
, который выполняет некоторое специальное выделение памяти, то, вероятнее всего, вы должны определить и специфичный для данного класса оператор T::operator delete
, который выполняет соответствующее освобождение выделенной памяти.
Появление данной рекомендации связано с одной тонкой проблемой: дело в том, что компилятор может вызвать перегруженный оператор T::operator delete
даже если вы никогда явно его не вызываете. Вот почему вы всегда должны предоставлять операторы new
и delete
(а также операторы new[]
и delete[]
) парами.
Пусть вы определили класс с пользовательским выделением памяти:
class T {
// ...
static void* operator new(std::size_t);
static void* operator new(std::size_t, CustomAllocator&);
static void operator delete(void*, std::size_t);
};
Вы вводите простой протокол для выделения и освобождения памяти.
• Вызывающий код может выделять объекты типа T
либо при помощи распределителя по умолчанию (используя вызов new T
), либо при помощи пользовательского распределителя (вызов new(allос) T
, где allос
— объект типа CustomAllocator
).
• Единственный оператор delete
, который может быть использован вызывающим кодом — оператор по умолчанию operator delete(size_t)
, так что, конечно, вы должны реализовать его так, чтобы он корректно освобождал память, выделенную любым способом.
Пока все в порядке.
Однако компилятор может скрыто вызвать другую перегрузку оператора delete
, а именно T::operator delete(size_t, CustomAllocator&)
. Это связано с тем, что инструкция
T* р = new(alloc) T;
на самом деле разворачивается в нечто наподобие
// Сгенерированный компилятором код для
// инструкции T* p = new(alloc)T;
//
void* __compilerTemp = T::operator new(sizeof(T), alloc);
T* p;
try {
p = new (__compilerTemp) T; // Создание объекта T по
// адресу __compilerTemp
} catch(...) { // Сбой в конструкторе...
T::operator delete(__compilerTemp, sizeof(T), alloc);
throw;
}