200
Выделение и освобождение памяти в классах
Перегрузка
операторов
new
и
delete
как функций класса несколько повышает ваш контроль над
происходящим. Изменения относятся только к данному классу и его производным классам, так что
побочные эффекты обычно оказываются минимальными. Такой вариант работает лучше всего при
выделении нестандартной схемы управления памятью в отдельный класс и его последующем
подключении средствами множественного наследования. Для некоторых
схем управления памятью
такая возможность исключается, но это уже трудности архитектора — показать, почему ее
не следует
реализовывать на базе подключаемых классов.
Если управление памятью реализовано в классе и вы можете создать от него производный класс,
деструктор следует сделать виртуальным, чтобы тот же класс мог и исвобождать память. Производные
классы не должны перегружать перегруженные версии.
Управление памятью под руководством клиента
Как демонстрируют приведенные выше фрагменты, клиентский код может выбирать, где объект
должен находиться в памяти. Обычно это делается с помощью перегруженного оператора
new
,
имеющего дополнительные
аргументы помимо
size_t
. В управлении памятью открываются новые
перспективы — управление на уровне отдельных объектов, а не класса в целом. К сожалению, эта
стратегия перекладывает на клиента хлопоты, связанные с освобождением памяти. Реализация
получается сложной, а модульность — низкой. Например, стоит изменить аргументы нестандартного
оператора
new
, и вам придется вносить изменения во
всех местах клиентского кода, где он
используется. Пока все перекомпилируется заново, можно погулять на свежем воздухе. Впрочем,
несмотря на все проблемы, эта стратегия легко реализуема, очень эффективна и хорошо работает в
простых ситуациях.
Объекты классов и производящие функции
Расположение объекта в памяти также может выбираться объектом
класса или производящей
функцией (или функциями). Возможны разные варианты, от простейших (например, предоставление
одной стратегии для всего класса) до выбора стратегии на основании аргументов, переданных
производящей функции. При использовании нескольких стратегий вы неизменно придете к стратегии
невидимых указателей, рассмотренной в следующем разделе. Более того, эти две концепции прекрасно
работают вместе.
Управление памятью с применением ведущих указателей
Похоже, я соврал; в этой главе нам все же придется вернуться к умным указателям.
Управление
памятью под руководством клиента можно усовершенствовать, инкапсулируя различные стратегии в
умных ведущих указателях. Расширение архитектуры с локальными пулами демонстрирует основную
идею, которая может быть приспособлена практически для любой схемы с управлением на уровне
объектов.
Специализированные ведущие указатели
Простейшая стратегия заключается в создании специализированного класса ведущего указателя или
шаблона, который знает о локальном пуле и использует глобальную перегрузку оператора
new
.
struct Pool { ... };
// Как и раньше
void* operator new(Pool* p); //
Выделение из пула
template
class PoolMP {
private:
Type*
pointee;
PoolMP(const PoolMP&) {} // Копирование не разрешено...
PoolMP& operator=(const PoolMP&)
{ return *this; }
// ...и присваивание тоже
public: