|
|
Если в вашей программе много небольших объектов, размещаемых в
свободной памяти, то может оказаться, что много времени тратится
на размещение и удаление таких объектов. Для выхода из этой
ситуации можно определить более оптимальный распределитель памяти
общего назначения, а можно передать обязанность распределения
свободной памяти создателю класса, который должен будет
определить соответствующие функции размещения и удаления.
Вернемся к классу name, который использовался в примерах с
table. Он мог бы определяться так:
struct name { char* string; name* next; double value; name(char*, double, name*); ~name(); void* operator new(size_t); void operator delete(void*, size_t); private: enum { NALL = 128 }; static name* nfree; };
Функции name::operator new() и name::operator delete() будут использоваться (неявно) вместо глобальных функций operator new() и operator delete(). Программист может для конкретного типа написать более эффективные по времени и памяти функции размещения и удаления, чем универсальные функции operator new() и operator delete(). Можно, например, разместить заранее "куски" памяти, достаточной для объектов типа name, и связать их в список; тогда операции размещения и удаления сводятся к простым операциям со списком. Переменная nfree используется как начало списка неиспользованных кусков памяти:
void* name::operator new(size_t) { register name* p = nfree; // сначала выделить if (p) nfree = p->next; else { // выделить и связать в список name* q = (name*) new char[NALL*sizeof(name) ]; for (p=nfree=&q[NALL-1]; q<p; p--) p->next = p-1; (p+1)->next = 0; } return p; }
Распределитель памяти, вызываемый new, хранит вместе с объектом его
размер, чтобы операция delete выполнялась правильно. Этого
дополнительного расхода памяти можно легко избежать, если
использовать распределитель, рассчитанный на конкретный тип. Так,
на машине автора функция name::operator new() для хранения объекта
name использует 16 байтов, тогда как стандартная глобальная
функция operator new() использует 20 байтов.
Отметим, что в самой функции name::operator new() память нельзя
выделять таким простым способом:
name* q= new name[NALL];
Это вызовет бесконечную рекурсию, т.к. new будет вызывать
name::name().
Освобождение памяти обычно тривиально:
void name::operator delete(void* p, size_t) { ((name*)p)->next = nfree; nfree = (name*) p; }
Приведение параметра типа void* к типу name* необходимо, поскольку функция освобождения вызывается после уничтожения объекта, так что больше нет реального объекта типа name, а есть только кусок памяти размером sizeof(name). Параметры типа size_t в приведенных функциях name::operator new() и name::operator delete() не использовались. Как можно их использовать, будет показано в $$6.7. Отметим, что наши функции размещения и удаления используются только для объектов типа name, но не для массивов names.
| |