Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
С++ Страуструп.doc
Скачиваний:
4
Добавлен:
18.04.2019
Размер:
2.72 Mб
Скачать

5.5.5 Массивы объектов класса

Чтобы можно было описать массив объектов класса с конструктором,

этот класс должен иметь стандартный конструктор, т.е. конструктор,

вызываемый без параметров. Например, в соответствии с определением

table tbl[10];

будет создан массив из 10 таблиц, каждая из которых инициализируется

вызовом table::table(15), поскольку вызов table::table() будет

происходить с фактическим параметром 15.

В описании массива объектов не предусмотрено возможности указать

параметры для конструктора. Если члены массива обязательно надо

инициализировать разными значениями, то начинаются трюки с

глобальными или статическими членами.

Когда уничтожается массив, деструктор должен вызываться для

каждого элемента массива. Для массивов, которые размещаются не

с помощью new, это делается неявно. Однако для размещенных в свободной

памяти массивов неявно вызывать деструктор нельзя, поскольку транслятор

не отличит указатель на отдельный объект массива от указателя на начало

массива, например:

void f()

{

table* t1 = new table;

table* t2 = new table[10];

delete t1; // удаляется одна таблица

delete t2; // неприятность:

// на самом деле удаляется 10 таблиц

}

В данном случае программист должен указать, что t2 - указатель

на массив:

void g(int sz)

{

table* t1 = new table;

table* t2 = new table[sz];

delete t1;

delete[] t2;

}

Функция размещения хранит число элементов для каждого размещаемого

массива. Требование использовать для удаления массивов только операцию

delete[] освобождает функцию размещения от обязанности хранить счетчики

числа элементов для каждого массива. Исполнение такой обязанности в

реализациях С++ вызывало бы существенные потери времени и памяти

и нарушило совместимость с С.

5.5.6 Небольшие объекты

Если в вашей программе много небольших объектов, размещаемых в

свободной памяти, то может оказаться, что много времени тратится

на размещение и удаление таких объектов. Для выхода из этой

ситуации можно определить более оптимальный распределитель памяти

общего назначения, а можно передать обязанность распределения

свободной памяти создателю класса, который должен будет

определить соответствующие функции размещения и удаления.

Вернемся к классу 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.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]