214
Проблем с зацикливанием не будет: поскольку ведущий указатель не хранит ссылок на свои
дескрипторы, связь является односторонней. Копируемые и передаваемые
дескрипторы сохраняют
длину в четыре байта без виртуальных функций, а лишь тривиальными подставляемыми функциями.
Grab
и
Release
съедают несколько дополнительных машинных тактов, но это мелочи по сравнению с
тем, что вам пришлось бы проделывать для управления ведущима указателями без них. Несколько
лишних байт для счетчика в ведущим указателе не играют особой роли; к тому же они выделяются в
куче, степень детализации которой обычно заметно превышает четыре байта.
Возможно, вам стоит вернуться к предыдущим главам и подумать, как использовать показанную схему
подсчета ссылок везде, где встечаются ведущие указатели. Это станет ключом к нетривиальному
управлению памятью в дальнейших главах.
Пространтсва памяти
Все эти фокусы образуют фундамент для дальнейшего строительства, но относить их к архитектуре
было бы неверно. Для действительно нетривиального управления памятью понадобятся нетривиальные
организационные концепции. В простейшем случае вся доступная память
рассматривается как один
большой блок, из которого выделяются блоки меньшего размера. Для этого можно либо напрямую
обратиться к операционной системе с требованием выделить большой блок памяти при запуске, либо
косвенно, в конечном счете перепоручая работу операторным функциям
::operator new
и
::operator delete
.
В двух последних главах мы взглянем на проблему с нетривиальных позиций и поделим доступную
память на
пространства (memory spaces). Пространства памяти — это концепция; ее
можно
реализовать на основе практически любой описанной выше блочно-ориентированной схемы
управления памятью. Например, в одном пространстве памяти может использоваться система
напарников, а в другом — списки свободной памяти. Концепция представлена в следующем
абстрактном базовом классе:
class MemSpace {
public:
void* Allocate(size_t bytes) = 0;
void Deallocate(void* space, size_t bytes) = 0;
};
(Если ваш компилятор поддерживает обработку исключений, при объявлении обоих функций следует
указать возможность инициирования исключений.) Некоторым пространствам памяти можно не
сообщать в функции
Deallocate()
размер возвращаемых блоков; для конкретных схем могут
появиться другие функции, но минимальный интерфейс выглядит именно так. Возможно, также будет
поддерживаться глобальная структура данных — коллекция всех
MemSpace
(причины
рассматриваются ниже). Коллекция должна эффективно отвечать на вопрос: «Какому пространству
памяти принадлежит данный адрес?» По имеющемуся адресу объекта вы определяете пространство
памяти, в котором он живет.
В реализации пространств памяти могут быть использованы любые методики,
описанные в
предыдущей главе:
•
Глобальная перегрузка операторов
new
и
delete
(обычно не рекомендуется).
•
Перегрузка операторов
new
и
delete
на уровне класса.
•
Использование оператора
new
с аргументами под руководством клиента.
•
Использование оператора
new
с аргументами на базе ведущих указателей.
Существует немало причин для деления памяти на пространства. Ниже описаны некоторые
распространенные стратегии выбора объектов, которые должны находиться в одном пространстве
памяти.
Деление по классам
В предыдущих главах мы говорили об объектах классов, но так и не ответили напрямую на вопрос: как
определить класс объекта для имеющегося объекта? Простейшее решение — добавить переменную,