Возможности по реализации автоматического управления памятью в C++

Несмотря на отсутствие сборки мусора в C++, его, в общем-то, несложно реализовать самому. Предлагаю один из примеров такой реализации.

1. Опишем базовый класс, который будет прародителем всех объектных классов. Назовем его, скажем, CUniObject. Единственным членом класса будет счетчик ссылок (unsigned int count). Счетчик ссылок будет приватным.

2. Описываем класс универсального указателя CUniPointer и вместо указателей на объекты будем создавать экземпляры этого класса. Для поддержки безопасности типов класс CUniPointer следует объявить шаблонным (например, так: template class CUniPointer), где в качестве параметра будет использоваться тип, на который мы хотим указывать. Тип, очевидно, будет производным от CUniObject, поэтому класс CUniPointer может смело ссылаться на basetype::count. Собственно указатель будет являться членом этого класса: basetype *pointer.

3. Чтобы класс указателя мог иметь непосредственный доступ к счетчику ссылок своего типа-параметра, класс CUniPointer следует объявить дружественным в каждом производном классе от CUniObject (friend class CUniPointer<ЭтотКласс>).

4. Объявим два конструктора: первый - конструктор по умолчанию - предназначен для создания неинициализированных объектов-указателей (pointer = NULL), второй - для инициализированных. Последний будет принимать параметром ссылку на другой объект. Другая версия инициализирующего конструктора будет принимать параметр-указатель (для того, чтобы мы могли объявить переменную вот так: CUniPointer ptr = new SomeType). Причем конструктор по умолчанию инициализирует pointer значением NULL, счетчик ссылок при этом, конечно, не затрагивается. Остальные конструкторы увеличивают счетчик ссылок объекта, находящегося по адресу pointer, на 1: pointer->count++.

5. В момент присваивания объекту-указателю нового значения счетчики ссылок старого объекта и нового объекта следует также менять. Для этого перегрузим оператор = в классе CUniPointer примерно таким образом:
template CUniPointer
&CUniPointer::operator = (basetype *new_ptr)
{
if (pointer != NULL)
pointer->count–;
pointer = new_ptr;
if (pointer != NULL)
pointer->count++;
return *this;
}

Таким образом, если раньше объект-указатель на что-то указывал, счетчик ссылок этого объекта уменьшим, поскольку теперь объект-указатель на него указывать не будет. Напротив, счетчик ссылок нового объекта увеличим, конечно, в том случае, если новый указатель не пуст.

6. Деструктор объекта-указателя должен также уменьшать счетчик ссылок, если pointer не пуст.

7. Остается добавить в оператор = и в деструктор специальный код, проверяющий счетчик ссылок объекта на равенство нулю, и уничтожающий объект в случае равенства. Таким образом, окончательный вид оператора = будет таков:
template CUniPointer
&CUniPointer::operator = (basetype *newptr)
{
if (pointer != NULL)
if (–(pointer->count) == 0)
delete pointer;
pointer = newptr;
if (pointer != NULL)
pointer-count++;
return *this;
}

Дополнительно ко всему этому можно перегрузить оператор = с параметром типа CUniPointer и реализовать оператор ->, который упростит доступ к членам объекта pointer, так, что объект CUniPointer будет работать как обычный указатель:
template
basetype *CUniPointer::operator -> ()
{
return pointer;
}

В результате этих действий мы, по сути, получим эмулятор сборщика мусора.

Замечу, что в стандарте C++ предусмотрен специальный класс - автоуказатель - который несколько похож на описанный выше CUniPointer и самостоятельно уничтожает содержащийся внутри него указатель в своём деструкторе. Это класс template class autoptr, и находится он в пространстве имен std. Есть очень важное отличие: autoptr не содержит счетчика ссылок и освобождает память в любом случае. Значит, если вы опишете функцию, в которой объявите экземпляр переменной autoptr, и значение указателя, содержащегося в ней, вернёте этой функцией, вы, по сути, вернете указатель на свободную память, не занятую никаким объектом, т. к. при выходе из вашей функции объект autoptr будет уничтожен и вместе с ним будет уничтожен ваш указатель. Поэтому autoptr - это не сборщик мусора, а именно автоматически уничтожаемый указатель. Тем не менее, он находит применения.

Остается только отметить, что и класс autoptr, и CUniPtr обладают хорошим свойством: прекрасно работают в условиях обработки исключений. Другими словами, объект autoptr или CUniPtr автоматически будет уменьшать счетчик ссылок и уничтожать указатель при условии счетчик = 0 даже тогда, когда произойдет исключение.

В следующей статье я расскажу об управлении памятью “изнутри” - как устроена система управления памятью с точки зрения операционных систем, языков программирования, сред разработки.

Апрель 11, 2008 — Рубрика: C++
Метки: ,