Язык программирования C++ для профессионалов

       

Функции размещения и освобождения


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

В разделах §5.5.6 и §6.7 было показано как с помощью определения функций X::operator new() и X::operator delete() можно использовать функцию размещения для объектов класса X. Здесь есть определенная трудность. Для двух классов X и Y функции размещения могут быть настолько сходными, что желательно иметь одну такую функцию. Иными словами, желательно иметь в библиотеке такой класс, который предоставляет функции размещения и освобождения, пригодные для размещения объектов данного класса. Если такой класс есть, то функции размещения и освобождения для данного класса получаются за счет привязки к нему общих функций размещения и освобождения:

class X { static Pool my_pool; // ... public: // ... void* operator new(size_t) { return my_pool.alloc(); } void operator delete(void* p) { my_pool.free(p); } };

Pool X::my_pool(sizeof(X));

С помощью класса Pool память распределяется блоками одного размера. В приведенном примере объект my_pool отводит память блоками размером sizeof(X).

Составляется описание класса X и используется Pool с учетом оптимизации скорости программы и компактности представления. Обратите внимание, что размер выделяемых блоков памяти является для класса "встроенным", поэтому задающий размер параметр функции X::operator new() не используется. Используется вариант функции X::operator delete() без параметра. Если класс Y является производным класса X, и sizeof(Y)>sizeof(X), то для класса Y должны быть свои функции размещения и освобождения. Наследование функций класса X приведет к катастрофе. К счастью, задать такие функции для Y очень просто.

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

Приведем описание класса Pool:


class Pool {

struct Link { Link* next; }

const unsigned esize; Link* head;

Pool(Pool&); // защита от копирования void operator= (Pool&); // защита от копирования void grow(); public: Pool(unsigned n); ~Pool();

void* alloc(); void free(void* b); };

inline void* Pool::alloc() { if (head==0) grow(); Link* p = head; head = p->next; return p; }

inline void Pool::free(void* b) { Link* p = (Link*) b; p->next = head; head = p; }

Приведенные описания логично поместить в заголовочный файл Pool.h. Следующие определения могут находиться в любом месте программе и завершают наш пример. Объект Pool должен инициализироваться конструктором:

Pool::Pool(unsigned sz) : esize(sz) { head = 0; }

Функция Pool::grow() будет связывать все элементы в список квантов свободной памяти head, образуя из них новый блок. Определения остальных функций-членов оставлены в качестве упражнений 5 и 6 в §13.11.

void Pool::grow() { const int overhead = 12; const int chunk_size = 8*1024-overhead; const int nelem = (chunk_size-esize)/esize;

char* start = new char[chunk_size]; char* last = &start[(nelem-1)*esize];

for (char* p = start; p<last; p+=esize) ((Link*)p)->next = ((Link*)p)+1; ((Link*)last)->next = 0; head = (Link*)start; }


Содержание раздела