• Выделение памяти, достаточной для хранения объекта типа T
без инициализации.
• Создание объекта типа T
в неинициализированной памяти.
• Уничтожение объекта типа T
и возвращение памяти в неинициализированное состояние.
• Освобождение неинициализированной памяти, достаточной для хранения объекта типа T
без инициализации.
Не удивительно, что класс allocator
— то, что нужно для реализации функции vector
. Начнем с того, что включим в класс vector
параметр класса allocator
.
template
A alloc; // используем объект класса allocator для работы
// с памятью, выделяемой для элементов
// ...
};
Кроме распределителя памяти, используемого вместо оператора new
, остальная часть описания класса vector
не отличается от прежнего. Как пользователи класса vector
, мы можем игнорировать распределители памяти, пока сами не захотим, чтобы класс vector
управлял памятью, выделенной для его элементов, нестандартным образом. Как разработчики класса vector
и как студенты, пытающиеся понять фундаментальные проблемы и освоить основные технологии программирования, мы должны понимать, как вектор работает с неинициализированной памятью, и предоставить пользователям правильно сконструированные объекты. Единственный код, который следует изменить, — это функции-члены класса vector
, непосредственно работающие с памятью, например функция vector
.
template
void vector
{
if (newalloc<=space) return; // размер не уменьшается
T* p = alloc.allocate(newalloc); // выделяем новую память
for (int i=0; i
// копируем
for (int i=0; i
alloc.deallocate(elem,space); // освобождаем старую память
elem = p;
space = newalloc;
}
Мы перемещаем элемент в новый участок памяти, создавая копию в неинициализированной памяти, а затем уничтожая оригинал. Здесь нельзя использовать присваивание, потому что для таких типов, как string
, присваивание подразумевает, что целевая область памяти уже проинициализирована.
Имея функции reserve()
, vector
, можно без труда написать следующий код.
template
void vector
{
if (space==0) reserve(8); // начинаем с памяти для 8 элементов
else if (sz==space) reserve(2*space); // выделяем больше памяти
alloc.construct(&elem[sz],val); // добавляем в конец
// значение val
++sz; // увеличиваем размер
}
Аналогично можно написать функцию vector
.
template
void vector
{
reserve(newsize);
for (int i=sz; i
// создаем
for (int i = newsize; i
// уничтожаем
sz = newsize;
}
Обратите внимание на то, что, поскольку некоторые типы не имеют конструкторов по умолчанию, мы снова предоставили возможность задавать начальное значение для новых элементов.
Другое новшество — деструктор избыточных элементов при уменьшении вектора. Представьте себе деструктор, превращающий объект определенного типа в простой набор ячеек памяти.
19.4. Проверка диапазона и исключения
Мы проанализировали текущее состояние нашего класса vector
и обнаружили (с ужасом?), что в нем не предусмотрена проверка выхода за пределы допустимого диапазона. Реализация оператора operator[]
не вызывает затруднений.
template
{
return elem[n];
}
Рассмотрим следующий пример:
vector
v[–200] = v[200]; // Ой!
int i;
cin>>i;
v[i] = 999; // повреждение произвольной ячейки памяти
Этот код компилируется и выполняется, обращаясь к памяти, не принадлежащей нашему объекту класса vector
. Это может создать большие неприятности! В реальной программе такой код неприемлем. Попробуем улучшить наш класс vector
, чтобы решить эту проблему. Простейший способ — добавить в класс операцию проверки доступа с именем at()
.