открытого интерфейса класса

 то

  сделайте ее не членом класса (и, при необходимости,

  в случаях 1 и 2 - другом)

  Если функция требует виртуального поведения,

  то

   добавьте виртуальную функцию-член для обеспечения

   виртуального поведения, и реализуйте функцию-не член

   с использованием этой виртуальной функции.

  иначе

   сделайте ее функцией-членом.

Примеры

Пример. basic_string. Стандартный класс basic_string чересчур монолитен и имеет 103 функции-члена, из которых 71 без потери эффективности можно сделать функциями, не являющимися ни членами, ни друзьями класса. Многие из них дублируют функциональность, уже имеющуюся в качестве алгоритма стандартной библиотеки, либо представляют собой алгоритмы, которые могли бы использоваться более широко, если бы не были спрятаны в классе basic_string. (См. рекомендации 5 и 32, а также [Sutter04].)

Ссылки

[Lakos96] §3.6.1, §9.1.2 • [McConnell93] §5.1-4 • [Murray93] §2.6 • [Meyers00] • [Stroustrup00] §10.3.2, §11.3.2, §11.3.5, §11.5.2, §21.2.3.1 • [Sutter00] §20 • [Sutter04] §37-40

<p>45. <code>new</code> и <code>delete</code> всегда должны разрабатываться вместе</p>Резюме

Каждая перегрузка 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;

}

Перейти на страницу:

Все книги серии C++ In-Depth

Похожие книги