Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Элджер Д. - C++. Библиотека программиста - 1999.pdf
Скачиваний:
141
Добавлен:
13.08.2013
Размер:
1.98 Mб
Скачать

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 с аргументами на базе ведущих указателей.

Существует немало причин для деления памяти на пространства. Ниже описаны некоторые распространенные стратегии выбора объектов, которые должны находиться в одном пространстве памяти.

Деление по классам

В предыдущих главах мы говорили об объектах классов, но так и не ответили напрямую на вопрос: как определить класс объекта для имеющегося объекта? Простейшее решение — добавить переменную,

215

которая ссылается на объект класса. К сожалению, оно связано с заметными затратами как по памяти, так и кода конструктора. Однако существуют два других варианта:

1. Хранить указатель на объект класса в ведущем указателе. Получаем те же затраты, но с улучшенной инкапсуляцией.

2.Выделить все экземпляры некоторого класса в одно пространство памяти и хранить указатель на объект класса в начале пространства (см. рисунок).

Класс

Объект

Пространство

памяти

Второй вариант существенно снижает затраты при условии, что по адресу объекта можно эффективно определить начало адресного пространства памяти, которому он принадлежит (возможно, с помощью упомянутой выше коллекции пространств памяти).

На первый взгляд кажется, что устраивать такую суету вокруг простейшей проблемы глупо. Почему бы просто не добавить дополнительную переменную, ссылающуюся на объект класса? Приведу по крайней мере две причины:

1. Класс, с которым вы работаете, может входить в коммерческую библиотеку, для которой у вас нет исходных текстов, или его модификация нежелательна по другим причинам.

2.Класс может представлять собой тривиальную объектно-ориентированную оболочку для примитивного типа данных (скажем, int). Лишние байты для указателя на объект класса (не говоря уже о v-таблице, которая неизбежно потребуется для виртуальных функций доступа к нему) могут оказаться весьма существенными.

Деление по размеру

Вполне разумно объединить все объекты одинакового размера (или принадлежащие одному диапазону размеров) в одно пространство памяти для оптимизации создания и уничтожения. Многие стратегии управления памятью работают для одних диапазонов лучше, чем для других. Например, при создании множества больших, сильно различающихся по размерам объектов схема со степенями 2 наверняка оставит много неиспользованных фрагментированных блоков. Однако для объектов относительно малых (или близких по размеру к степеням 2) такая схема работает просто замечательно. В крайних случаях все пространство памяти может заполняться объектами одинакового размера. Такие пространства обладают чрезвычайно эффективным представлением и легко управляются.

Деление по способу использования

Еще один возможный вариант — делить объекты в соответствии со способом их использования. Например, для многопоточного приложения-сервера объекты можно разделить по клиентам. Редко используемые объекты могут находиться в одном пространстве, а часто используемые — в другом. Объекты, кэшируемые на диске, могут отделяться от объектов, постоянно находящихся в памяти. Деление может осуществляться как на уровне классов, так и на уровне отдельных объектов.

Деление по средствам доступа

Другая важная причина для разделения по пространствам памяти заключается в том, чтобы хранить по отдельности объекты, доступ к которым осуществляется из стека, из других процессов или чисто

216

внутренние из кучи. Как будет показано в последних главах, это играет важную роль в схемах уплотнения и сборки мусора.

В двух следующих главах эта методика будет применяться довольно часто. Перемещаемые объекты отделяются от объектов, остающихся на одном месте. Ведущие указатели, доступные из стека, находятся в одном пространстве памяти, а доступные из других процессов — в другом.

Пространства стека и кучи

Наконец, сам стек тоже можно считать разновидностью пространства памяти, а выделяемые в стеке пулы — их частным случаем. Этот подход применялся ранее в этой главе для пулов, локальных по отношению к области действия конкретной функции. Эта особая интерпретация стека упоминается и в двух последующих главах.

Соседние файлы в предмете Программирование на C++