• помните, что функциям allocate
передается количество T*
(через определение типа pointer
) несмотря на то, что ни один объект T
еще не сконструирован;
• обязательно предоставьте вложенный шаблон rebind
, от наличия которого зависит работа стандартных контейнеров.
Написание собственного распределителя памяти обычно сводится к копированию приличного объема стандартного кода и последующей модификации нескольких функций (в первую очередь allocate
и deallocate
). Вместо того чтобы писать базовый код с самого начала, я рекомендую воспользоваться кодом с web-страницы Джосаттиса [23] или из статьи Остерна «What Are Allocators Good For?» [24].
Материал, изложенный в этом совете, дает представление о том, чего
Совет 11. Учитывайте область применения пользовательских распределителей памяти
Итак, в результате хронометража, профилирования и всевозможных экспериментов вы пришли к выводу, что стандартный распределитель памяти STL (то есть allocator
) работает слишком медленно, напрасно расходует или фрагментирует память, и вы лучше справитесь с этой задачей. А может быть, allocator
обеспечивает безопасность в многопоточной модели, но вы планируете использовать только однопоточную модель и не желаете расходовать ресурсы на синхронизацию, которая вам не нужна. Или вы знаете, что объекты некоторых контейнеров обычно используются вместе, и хотите расположить их рядом друг с другом в специальной куче, чтобы по возможности локализовать ссылки. Или вы хотите выделить блок общей памяти и разместить в нем свои контейнеры, чтобы они могли использоваться другими процессами. Превосходно! В каждом из этих сценариев уместно воспользоваться нестандартным распределителем памяти.
Предположим, у вас имеются специальные функции для управления блоком общей памяти, написанные по образцу malloc
и free
:
void* mallocShared(size_t bytesNeeded);
void freeShared(void *ptr);
Требуется, чтобы память для содержимого контейнеров STL выделялась в общем блоке. Никаких проблем:
template
class SharedMemoryAllocator {
public:
…
pointer allocate(size_type numObjects, const void* localityHint=0) {
return static_cast
}
void deallocate(pointer ptrToMemory, size_type numObjects) {
freeShared(ptrToMemory);
}
…
};
За информацией о типе pointer, а также о преобразовании типа и умножении при вызове allocate обращайтесь к совету 10. Пример использования SharedMemoryAllocator
:
// Вспомогательное определение типа
typedef
vector
…
{ // Начало блока
SharedDoubleVec v;// Создать вектор, элементы которого
… // находятся в общей памяти
} // Конец блока
Обратите особое внимание на формулировку комментария рядом с определением v
. Вектор v
использует SharedMemoryAllocator
, потому память для хранения элементов v
будет выделяться из общей памяти, однако сам вектор v
(вместе со всеми переменными класса) почти наверняка v
— обычный стековый объект, поэтому он будет находиться в памяти, в которой исполнительная система хранит все обычные стековые объекты. Такая память почти никогда не является общей. Чтобы разместить в общей памяти как содержимое v
, так и сам объект v
, следует поступить примерно так:
void *pVectorMemory = // Выделить блок общей памяти,
mallocShared(sizeof(SharedOoubleVec)); // обьем которой достаточен
// для хранения объекта SharedDoubleVec
SharedDoubleVec *pv = // Использовать "new с явным
new (pVectorMemory) SharedDoubleVec; // размещением" для создания
// объекта SharedDoubleVec:
// см. далее.
… // Использование объекта (через pv)
pv->~SharedDoubleVec; // Уничтожить объект в общей памяти